nautilus_core/ffi/
cvec.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use std::{ffi::c_void, fmt::Display, ptr::null};
17
18/// `CVec` is a C compatible struct that stores an opaque pointer to a block of
19/// memory, it's length and the capacity of the vector it was allocated from.
20///
21/// NOTE: Changing the values here may lead to undefined behavior when the
22/// memory is dropped.
23#[repr(C)]
24#[derive(Clone, Copy, Debug)]
25pub struct CVec {
26    /// Opaque pointer to block of memory storing elements to access the
27    /// elements cast it to the underlying type.
28    pub ptr: *mut c_void,
29    /// The number of elements in the block.
30    pub len: usize,
31    /// The capacity of vector from which it was allocated.
32    /// Used when deallocating the memory
33    pub cap: usize,
34}
35
36/// Empty derivation for Send to satisfy `pyclass` requirements
37/// however this is only designed for single threaded use for now
38unsafe impl Send for CVec {}
39
40impl CVec {
41    #[must_use]
42    pub const fn empty() -> Self {
43        Self {
44            // Explicitly type cast the pointer to some type to satisfy the
45            // compiler. Since the pointer is null it works for any type.
46            ptr: null::<bool>() as *mut c_void,
47            len: 0,
48            cap: 0,
49        }
50    }
51}
52
53/// Consumes and leaks the Vec, returning a mutable pointer to the contents as
54/// a [`CVec`]. The memory has been leaked and now exists for the lifetime of the
55/// program unless dropped manually.
56/// Note: drop the memory by reconstructing the vec using `from_raw_parts` method
57/// as shown in the test below.
58impl<T> From<Vec<T>> for CVec {
59    fn from(mut data: Vec<T>) -> Self {
60        if data.is_empty() {
61            Self::empty()
62        } else {
63            let len = data.len();
64            let cap = data.capacity();
65            let ptr = data.as_mut_ptr();
66            std::mem::forget(data);
67            Self {
68                ptr: ptr.cast::<std::ffi::c_void>(),
69                len,
70                cap,
71            }
72        }
73    }
74}
75
76impl Display for CVec {
77    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78        write!(
79            f,
80            "CVec {{ ptr: {:?}, len: {}, cap: {} }}",
81            self.ptr, self.len, self.cap,
82        )
83    }
84}
85
86////////////////////////////////////////////////////////////////////////////////
87// C API
88////////////////////////////////////////////////////////////////////////////////
89#[cfg(feature = "ffi")]
90#[no_mangle]
91pub extern "C" fn cvec_drop(cvec: CVec) {
92    let CVec { ptr, len, cap } = cvec;
93    let data: Vec<u8> = unsafe { Vec::from_raw_parts(ptr.cast::<u8>(), len, cap) };
94    drop(data); // Memory freed here
95}
96
97#[cfg(feature = "ffi")]
98#[no_mangle]
99pub const extern "C" fn cvec_new() -> CVec {
100    CVec::empty()
101}
102
103#[cfg(test)]
104mod tests {
105    use rstest::*;
106
107    use super::CVec;
108
109    /// Access values from a vector converted into a [`CVec`].
110    #[rstest]
111    #[allow(unused_assignments)]
112    fn access_values_test() {
113        let test_data = vec![1_u64, 2, 3];
114        let mut vec_len = 0;
115        let mut vec_cap = 0;
116        let cvec: CVec = {
117            let data = test_data.clone();
118            vec_len = data.len();
119            vec_cap = data.capacity();
120            data.into()
121        };
122
123        let CVec { ptr, len, cap } = cvec;
124        assert_eq!(len, vec_len);
125        assert_eq!(cap, vec_cap);
126
127        let data = ptr.cast::<u64>();
128        unsafe {
129            assert_eq!(*data, test_data[0]);
130            assert_eq!(*data.add(1), test_data[1]);
131            assert_eq!(*data.add(2), test_data[2]);
132        }
133
134        unsafe {
135            // reconstruct the struct and drop the memory to deallocate
136            let _ = Vec::from_raw_parts(ptr.cast::<u64>(), len, cap);
137        }
138    }
139
140    /// After deallocating the vector the block of memory may not
141    /// contain the same values.
142    /// NOTE: This test maybe flaky depending on the platform
143    #[rstest]
144    #[ignore] // TODO: Flaky one some platforms
145    fn drop_test() {
146        let test_data = vec![1, 2, 3];
147        let cvec: CVec = {
148            let data = test_data.clone();
149            data.into()
150        };
151
152        let CVec { ptr, len, cap } = cvec;
153        let data = ptr.cast::<u64>();
154
155        unsafe {
156            let data: Vec<u64> = Vec::from_raw_parts(ptr.cast::<u64>(), len, cap);
157            drop(data);
158        }
159
160        unsafe {
161            assert_ne!(*data, test_data[0]);
162            assert_ne!(*data.add(1), test_data[1]);
163            assert_ne!(*data.add(2), test_data[2]);
164        }
165    }
166
167    /// An empty vector gets converted to a null pointer wrapped in a [`CVec`].
168    #[rstest]
169    fn empty_vec_should_give_null_ptr() {
170        let data: Vec<u64> = vec![];
171        let cvec: CVec = data.into();
172        assert_eq!(cvec.ptr.cast::<u64>(), std::ptr::null_mut::<u64>());
173    }
174}