safa_abi/ffi/
option.rs

1use crate::ffi::NotZeroable;
2
3/// An FFI Safe [Option]-like type
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
5#[repr(C, u8)]
6pub enum COption<T> {
7    #[default]
8    None = 0,
9    Some(T) = 1,
10}
11
12impl<T> From<Option<T>> for COption<T> {
13    #[inline(always)]
14    fn from(value: Option<T>) -> Self {
15        match value {
16            None => Self::None,
17            Some(x) => Self::Some(x),
18        }
19    }
20}
21
22impl<T> From<COption<T>> for Option<T> {
23    #[inline(always)]
24    fn from(value: COption<T>) -> Self {
25        match value {
26            COption::None => None,
27            COption::Some(x) => Some(x),
28        }
29    }
30}
31
32/// Represents an Option where a zero value is considered None.
33///
34/// Wraps an FFI type that SHOULD be non zeroable, and provides a safe way to handle zero values as None
35///
36/// The reason why i used "SHOULD" is because that type may be zeroed if passed from a foreign callsite so extra handling is required, this type makes it safe for it to be zeroed.
37#[derive(Clone, Copy, Hash, Default)]
38#[repr(transparent)]
39pub struct OptZero<T: NotZeroable>(T);
40
41impl<T: NotZeroable> OptZero<T> {
42    /// Creates a new `OptZero` that represents a Some value from a value.
43    pub const fn some(x: T) -> Self {
44        Self(x)
45    }
46    /// Creates a new `OptZero` that represents a None value.
47    pub const fn none() -> Self {
48        Self(unsafe { core::mem::zeroed() })
49    }
50    /// Creates a new `OptZero` from an `Option`.
51    pub fn from_option(opt: Option<T>) -> Self {
52        match opt {
53            None => Self::none(),
54            Some(x) => Self::some(x),
55        }
56    }
57    /// Converts this `OptZero` into an `Option`.
58    pub fn into_option(self) -> Option<T> {
59        match self.0.is_zero() {
60            false => Some(self.0),
61            true => None,
62        }
63    }
64    /// Returns a reference to the contained value or `None` if the value is zeroed.
65    pub fn as_option(&self) -> Option<&T> {
66        match self.0.is_zero() {
67            false => Some(&self.0),
68            true => None,
69        }
70    }
71
72    /// Returns the inner value whether or not it is zeroed.
73    /// # Safety
74    /// unsafe because OptZero works with the promise that it is going to handle zeroed values.
75    pub unsafe fn into_inner_unchecked(self) -> T {
76        self.0
77    }
78
79    /// Maps a `OptZero` to another `OptZero` using a function.
80    pub fn map<F, U>(self, f: F) -> OptZero<U>
81    where
82        F: FnOnce(T) -> U,
83        U: NotZeroable,
84    {
85        match self.0.is_zero() {
86            false => OptZero::some(f(self.0)),
87            true => OptZero::none(),
88        }
89    }
90}
91
92impl<T: NotZeroable + PartialEq> PartialEq for OptZero<T> {
93    #[inline]
94    fn eq(&self, other: &Self) -> bool {
95        (self.0.is_zero() && other.0.is_zero()) || (self.0 == other.0)
96    }
97}
98
99impl<T: NotZeroable + Eq> Eq for OptZero<T> {}
100
101impl<T: NotZeroable> From<OptZero<T>> for Option<T> {
102    #[inline(always)]
103    fn from(value: OptZero<T>) -> Self {
104        value.into_option()
105    }
106}
107
108impl<T: NotZeroable> From<Option<T>> for OptZero<T> {
109    #[inline(always)]
110    fn from(value: Option<T>) -> Self {
111        Self::from_option(value)
112    }
113}