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}