ffizz_passby/
boxed.rs

1use std::default::Default;
2use std::marker::PhantomData;
3
4/// Boxed is used to model values that are passed by reference and where their memory allocation is
5/// managed entirely by Rust.  These are represented in the C API by a pointer, with "new" and
6/// "free" functions handling creation and destruction.
7///
8/// The value may be opaque to C, so that it may not access fields in the struct directly, in which
9/// case `RType` can be any Rust type.  Otherwise, if a C structure is provided, you must use
10/// `#[repr(C)]` to ensure that C and Rust lay out the struct identically.
11///
12/// # Example
13///
14/// Define your C and Rust types, then a type alias parameterizing Boxed:
15///
16/// ```
17/// # use ffizz_passby::Boxed;
18/// struct System {
19///     // ...
20/// }
21/// type BoxedSystem = Boxed<System>;
22/// ```
23///
24/// Then call static methods on that type alias.
25#[non_exhaustive]
26pub struct Boxed<RType: Sized> {
27    _phantom: PhantomData<RType>,
28}
29
30impl<RType: Sized> Boxed<RType> {
31    /// Take a value from C as an argument, taking ownership of the value it points to.
32    ///
33    /// Be careful that the C API documents that the passed pointer cannot be used after this
34    /// function is called.
35    ///
36    /// If you would like to borrow the value, but leave ownership with the calling C code, use
37    /// [`Boxed::with_ref`] or its variants.
38    ///
39    /// This function is most common in "free" functions, but can also be used in contexts where it
40    /// is ergonomic for the called function to consume the value.  For example, a database
41    /// connections's `execute` method might reasonably consume a query argument.
42    ///
43    /// ```c
44    /// db_query_t q = db_query_new();
45    /// db_query_set_filter(q, "x = 10");
46    /// db_query_add_column(q, "y");
47    /// db_result_t res = db_execute(db, q);
48    /// ```
49    ///
50    /// Here it's natural to assume (but should also be documented) that the `db_execute`
51    /// function takes ownership of the query.
52    ///
53    /// # Safety
54    ///
55    /// * `arg` must not be NULL (see [`Boxed::take`] for a version allowing NULL).
56    /// * `arg` must be a value returned from `Box::into_raw` (via [`Boxed::return_val`] or [`Boxed::to_out_param`] or a variant).
57    /// * `arg` becomes invalid and must not be used after this call.
58    pub unsafe fn take_nonnull(arg: *mut RType) -> RType {
59        debug_assert!(!arg.is_null());
60        // SAFETY: see docstring
61        unsafe { *(Box::from_raw(arg)) }
62    }
63
64    /// Call the contained function with a shared reference to the value.
65    ///
66    /// # Safety
67    ///
68    /// * `arg` must not be NULL (see [`Boxed::with_ref`] for a version allowing NULL).
69    /// * No other thread may mutate the value pointed to by `arg` until this function returns.
70    /// * Ownership of the value remains with the caller.
71    pub unsafe fn with_ref_nonnull<T, F: FnOnce(&RType) -> T>(arg: *const RType, f: F) -> T {
72        if arg.is_null() {
73            panic!("NULL value not allowed");
74        }
75        // SAFETY:
76        // - pointer came from Box::into_raw, so has proper size and alignment
77        f(unsafe { &*(arg as *const RType) })
78    }
79
80    /// Call the contained function with an exclusive reference to the value.
81    ///
82    /// # Safety
83    ///
84    /// * `arg` must not be NULL (see [`Boxed::with_ref_mut`] for a version allowing null)
85    /// * No other thread may _access_ the value pointed to by `arg` until this function returns.
86    /// * Ownership of the value remains with the caller.
87    pub unsafe fn with_ref_mut_nonnull<T, F: FnOnce(&mut RType) -> T>(arg: *mut RType, f: F) -> T {
88        if arg.is_null() {
89            panic!("NULL value not allowed");
90        }
91        // SAFETY:
92        // - pointer came from Box::into_raw, so has proper size and alignment
93        f(unsafe { &mut *arg })
94    }
95
96    /// Return a value to C, boxing the value and transferring ownership.
97    ///
98    /// This method is most often used in constructors, to return the built value.
99    ///
100    /// # Safety
101    ///
102    /// * The caller must ensure that the value is eventually freed.
103    pub unsafe fn return_val(rval: RType) -> *mut RType {
104        // SAFETY: return_val_boxed and return_val have the same safety requirements.
105        unsafe { Self::return_val_boxed(Box::new(rval)) }
106    }
107
108    /// Return a boxed value to C, transferring ownership.
109    ///
110    /// This is an alternative to [`Boxed::return_val`] for use when the value is already boxed.
111    ///
112    /// # Safety
113    ///
114    /// * The caller must ensure that the value is eventually freed.
115    pub unsafe fn return_val_boxed(rval: Box<RType>) -> *mut RType {
116        Box::into_raw(rval)
117    }
118
119    /// Return a value to C, transferring ownership, via an "output parameter".
120    ///
121    /// If the pointer is NULL, the value is dropped.  Use [`Boxed::to_out_param_nonnull`] to panic
122    /// in this situation.
123    ///
124    /// # Safety
125    ///
126    /// * The caller must ensure that the value is eventually freed.
127    /// * If not NULL, `arg_out` must point to valid, properly aligned memory for a pointer value.
128    pub unsafe fn to_out_param(rval: RType, arg_out: *mut *mut RType) {
129        if !arg_out.is_null() {
130            // SAFETY: see docstring
131            unsafe { *arg_out = Self::return_val(rval) };
132        }
133    }
134
135    /// Return a value to C, transferring ownership, via an "output parameter".
136    ///
137    /// If the pointer is NULL, this function will panic.  Use [`Boxed::to_out_param`] to
138    /// drop the value in this situation.
139    ///
140    /// # Safety
141    ///
142    /// * The caller must ensure that the value is eventually freed.
143    /// * `arg_out` must not be NULL.
144    /// * `arg_out` must point to valid, properly aligned memory for a pointer value.
145    pub unsafe fn to_out_param_nonnull(rval: RType, arg_out: *mut *mut RType) {
146        if arg_out.is_null() {
147            panic!("out param pointer is NULL");
148        }
149        // SAFETY: see docstring
150        unsafe { *arg_out = Self::return_val(rval) };
151    }
152}
153
154impl<RType: Sized + Default> Boxed<RType> {
155    /// Take a value from C as an argument.
156    ///
157    /// This function is similar to [`Boxed::take_nonnull`], but returns the default value of RType when
158    /// given NULL.
159    ///
160    /// # Safety
161    ///
162    /// * `arg` must be a value returned from `Box::into_raw` (via [`Boxed::return_val`] or [`Boxed::to_out_param`] or a variant).
163    /// * `arg` becomes invalid and must not be used after this call.
164    pub unsafe fn take(arg: *mut RType) -> RType {
165        debug_assert!(!arg.is_null());
166        // SAFETY: see docstring
167        unsafe { *(Box::from_raw(arg)) }
168    }
169
170    /// Call the contained function with a shared reference to the value.
171    ///
172    /// If the given pointer is NULL, the contained function is called with a reference to RType's
173    /// default value, which is subsequently dropped.
174    ///
175    /// # Safety
176    ///
177    /// * No other thread may mutate the value pointed to by `arg` until this function returns.
178    /// * Ownership of the value remains with the caller.
179    pub unsafe fn with_ref<T, F: FnOnce(&RType) -> T>(arg: *const RType, f: F) -> T {
180        if arg.is_null() {
181            let nullval = RType::default();
182            return f(&nullval);
183        }
184
185        // SAFETY:
186        // - pointer is not NULL (just checked)
187        // - pointer came from Box::into_raw, so has proper size and alignment
188        f(unsafe { &*(arg as *const RType) })
189    }
190
191    /// Call the contained function with an exclusive reference to the value.
192    ///
193    /// If the given pointer is NULL, the contained function is called with a reference to RType's
194    /// default value, which is subsequently dropped.
195    ///
196    /// # Safety
197    ///
198    /// * No other thread may _access_ the value pointed to by `arg` until this function returns.
199    /// * Ownership of the value remains with the caller.
200    pub unsafe fn with_ref_mut<T, F: FnOnce(&mut RType) -> T>(arg: *mut RType, f: F) -> T {
201        if arg.is_null() {
202            let mut nullval = RType::default();
203            return f(&mut nullval);
204        }
205
206        // SAFETY:
207        // - pointer is not NULL (just checked)
208        // - pointer came from Box::into_raw, so has proper size and alignment
209        f(unsafe { &mut *arg })
210    }
211}
212
213#[cfg(test)]
214mod test {
215    use super::*;
216    use std::mem;
217
218    #[derive(Default)]
219    struct RType(u32, u64);
220
221    type BoxedTuple = Boxed<RType>;
222
223    #[test]
224    fn intialize_and_with_methods() {
225        unsafe {
226            let mut cptr = mem::MaybeUninit::<*mut RType>::uninit();
227            BoxedTuple::to_out_param(RType(10, 20), cptr.as_mut_ptr());
228            let cptr = cptr.assume_init();
229
230            BoxedTuple::with_ref_nonnull(cptr, |rref| {
231                assert_eq!(rref.0, 10);
232                assert_eq!(rref.1, 20);
233            });
234
235            BoxedTuple::with_ref_mut_nonnull(cptr, |rref| {
236                assert_eq!(rref.0, 10);
237                assert_eq!(rref.1, 20);
238                rref.0 = 30;
239            });
240
241            BoxedTuple::with_ref_mut(cptr, |rref| {
242                assert_eq!(rref.0, 30);
243                rref.0 += 1;
244                assert_eq!(rref.1, 20);
245                rref.1 += 1;
246            });
247
248            BoxedTuple::with_ref(cptr, |rref| {
249                assert_eq!(rref.0, 31);
250                assert_eq!(rref.1, 21);
251            });
252
253            let rval = BoxedTuple::take(cptr);
254            assert_eq!(rval.0, 31);
255            assert_eq!(rval.1, 21);
256
257            let mut cptr = mem::MaybeUninit::<*mut RType>::uninit();
258            BoxedTuple::to_out_param_nonnull(RType(100, 200), cptr.as_mut_ptr());
259            let cptr = cptr.assume_init();
260
261            let rval = BoxedTuple::take(cptr);
262            assert_eq!(rval.0, 100);
263            assert_eq!(rval.1, 200);
264        }
265    }
266
267    #[test]
268    fn with_null_ptrs() {
269        unsafe {
270            BoxedTuple::with_ref_mut(std::ptr::null_mut(), |rref| {
271                assert_eq!(rref.0, 0);
272                assert_eq!(rref.1, 0);
273                rref.1 += 1;
274            });
275
276            BoxedTuple::with_ref(std::ptr::null(), |rref| {
277                assert_eq!(rref.0, 0);
278                assert_eq!(rref.1, 0);
279            });
280        }
281    }
282
283    #[test]
284    #[should_panic]
285    fn with_ref_nonnull_null() {
286        unsafe {
287            BoxedTuple::with_ref_nonnull(std::ptr::null(), |_| {});
288        }
289    }
290
291    #[test]
292    #[should_panic]
293    fn with_ref_mut_nonnull_null() {
294        unsafe {
295            BoxedTuple::with_ref_mut_nonnull(std::ptr::null_mut(), |_| {});
296        }
297    }
298
299    #[test]
300    fn to_out_param_null() {
301        unsafe {
302            BoxedTuple::to_out_param(RType(10, 20), std::ptr::null_mut());
303            // nothing happens
304        }
305    }
306
307    #[test]
308    #[should_panic]
309    fn to_out_param_nonnull_null() {
310        unsafe {
311            BoxedTuple::to_out_param_nonnull(RType(10, 20), std::ptr::null_mut());
312            // nothing happens
313        }
314    }
315
316    #[test]
317    fn return_val_take() {
318        unsafe {
319            let cptr = BoxedTuple::return_val(RType(10, 20));
320            let rval = BoxedTuple::take(cptr);
321            assert_eq!(rval.0, 10);
322            assert_eq!(rval.1, 20);
323        }
324    }
325
326    #[test]
327    fn return_val_boxed_take_nonnull() {
328        unsafe {
329            let cptr = BoxedTuple::return_val_boxed(Box::new(RType(10, 20)));
330            let rval = BoxedTuple::take_nonnull(cptr);
331            assert_eq!(rval.0, 10);
332            assert_eq!(rval.1, 20);
333        }
334    }
335
336    #[test]
337    #[should_panic]
338    fn take_nnull() {
339        unsafe {
340            let rval = BoxedTuple::take(std::ptr::null_mut());
341            assert_eq!(rval.0, 0);
342            assert_eq!(rval.1, 0);
343        }
344    }
345
346    #[test]
347    #[should_panic]
348    fn take_nonnull_null() {
349        unsafe {
350            BoxedTuple::take_nonnull(std::ptr::null_mut());
351        }
352    }
353}