ffizz_passby/
value.rs

1use std::marker::PhantomData;
2
3/// Value is used to "pass by value' semantics.
4///
5/// This is typically used for Copy types, such as integers or enums. For types that are not Copy,
6/// [`crate::Unboxed`] is a better choice.
7///
8/// The two type parameters must be convertible using `Into<RType> for CType` and `From<RType> for
9/// CType`. This choice of traits was made deliberately, on the assumption that `CType` is defined
10/// locally to your crate, while `RType` may be a type from another crate.
11///
12/// # Example
13///
14/// Define your C and Rust types, then a type alias parameterizing Value:
15///
16/// ```
17/// # type Uuid = i128;
18/// # use ffizz_passby::Value;
19/// #[repr(C)]
20/// pub struct uuid_t([u8; 16]);
21///
22/// type UuidValue = Value<Uuid, uuid_t>;
23/// ```
24///
25/// Then call static mtehods on that type alias.
26#[non_exhaustive]
27pub struct Value<RType, CType>
28where
29    RType: Sized,
30    CType: Sized + From<RType> + Into<RType>,
31{
32    _phantom: PhantomData<(RType, CType)>,
33}
34
35impl<RType, CType> Value<RType, CType>
36where
37    // In typical usage, RType might be a type that is external to the user's crate,
38    // so we cannot require any custom traits on that type.
39    RType: Sized,
40    CType: Sized + From<RType> + Into<RType>,
41{
42    /// Take a CType and return an owned value.
43    ///
44    /// The caller retains a copy of the value.
45    pub fn take(cval: CType) -> RType {
46        cval.into()
47    }
48
49    /// Return a CType containing rval, moving rval in the process.
50    pub fn return_val(rval: RType) -> CType {
51        CType::from(rval)
52    }
53
54    /// Initialize the value pointed to `arg_out` with rval, "moving" rval into the pointer.
55    ///
56    /// If the pointer is NULL, rval is dropped.  Use [`Value::to_out_param_nonnull`] to
57    /// panic in this situation.
58    ///
59    /// # Safety
60    ///
61    /// * if `arg_out` is not NULL, then it must be aligned for and have enough space for
62    ///   CType.
63    pub unsafe fn to_out_param(rval: RType, arg_out: *mut CType) {
64        if !arg_out.is_null() {
65            // SAFETY:
66            //  - arg_out is not NULL (just checked)
67            //  - arg_out is properly aligned and points to valid memory (see docstring)
68            unsafe { *arg_out = CType::from(rval) };
69        }
70    }
71
72    /// Initialize the value pointed to `arg_out` with rval, "moving" rval into the pointer.
73    ///
74    /// If the pointer is NULL, this method will panic.
75    ///
76    /// # Safety
77    ///
78    /// * `arg_out` must not be NULL, must be aligned for CType and have enough space for CType.
79    pub unsafe fn to_out_param_nonnull(rval: RType, arg_out: *mut CType) {
80        if arg_out.is_null() {
81            panic!("out param pointer is NULL");
82        }
83        // SAFETY:
84        //  - arg_out is not NULL (see docstring)
85        //  - arg_out is properly aligned and points to valid memory (see docstring)
86        unsafe { *arg_out = CType::from(rval) };
87    }
88}
89
90#[cfg(test)]
91mod test {
92    use super::*;
93    use std::mem;
94
95    #[allow(non_camel_case_types)]
96    #[derive(Clone, Debug, PartialEq, Eq)]
97    struct result_t {
98        is_ok: bool,
99        error_code: u32,
100    }
101
102    impl Into<Result<(), u32>> for result_t {
103        fn into(self) -> Result<(), u32> {
104            if self.is_ok {
105                Ok(())
106            } else {
107                Err(self.error_code)
108            }
109        }
110    }
111
112    impl From<Result<(), u32>> for result_t {
113        fn from(res: Result<(), u32>) -> result_t {
114            match res {
115                Ok(_) => result_t {
116                    is_ok: true,
117                    error_code: 0,
118                },
119                Err(error_code) => result_t {
120                    is_ok: false,
121                    error_code,
122                },
123            }
124        }
125    }
126
127    type ResultValue = Value<Result<(), u32>, result_t>;
128
129    #[test]
130    fn take_and_return() {
131        let cval = result_t {
132            is_ok: false,
133            error_code: 13,
134        };
135        let rval = ResultValue::take(cval.clone());
136        assert_eq!(rval, Err(13));
137        assert_eq!(ResultValue::return_val(rval), cval);
138    }
139
140    #[test]
141    fn to_out_param() {
142        let mut cval = mem::MaybeUninit::uninit();
143        // SAFETY: arg_out is not NULL
144        unsafe {
145            ResultValue::to_out_param(Ok(()), cval.as_mut_ptr());
146        }
147        // SAFETY: to_out_param initialized cval
148        assert_eq!(ResultValue::take(unsafe { cval.assume_init() }), Ok(()));
149    }
150
151    #[test]
152    fn to_out_param_null() {
153        // SAFETY: passing null results in no action
154        unsafe {
155            ResultValue::to_out_param(Ok(()), std::ptr::null_mut());
156        }
157    }
158
159    #[test]
160    fn to_out_param_nonnull() {
161        let mut cval = mem::MaybeUninit::uninit();
162        // SAFETY: arg_out is not NULL
163        unsafe {
164            ResultValue::to_out_param_nonnull(Ok(()), cval.as_mut_ptr());
165        }
166        // SAFETY: to_out_param initialized cval
167        assert_eq!(ResultValue::take(unsafe { cval.assume_init() }), Ok(()));
168    }
169
170    #[test]
171    #[should_panic]
172    fn to_out_param_nonnull_null() {
173        // SAFETY: well, it's not safe, that's why it panics!
174        unsafe {
175            ResultValue::to_out_param_nonnull(Ok(()), std::ptr::null_mut());
176        }
177    }
178}