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}