zerovec/ule/
option.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5use super::*;
6use core::cmp::Ordering;
7use core::marker::PhantomData;
8use core::mem::{self, MaybeUninit};
9
10/// This type is the [`ULE`] type for `Option<U>` where `U` is a [`ULE`] type
11///
12/// # Example
13///
14/// ```rust
15/// use zerovec::ZeroVec;
16///
17/// let z = ZeroVec::alloc_from_slice(&[
18///     Some('a'),
19///     Some('á'),
20///     Some('ø'),
21///     None,
22///     Some('ł'),
23/// ]);
24///
25/// assert_eq!(z.get(2), Some(Some('ø')));
26/// assert_eq!(z.get(3), Some(None));
27/// ```
28// Invariants:
29// The MaybeUninit is zeroed when None (bool = false),
30// and is valid when Some (bool = true)
31#[repr(C, packed)]
32pub struct OptionULE<U>(bool, MaybeUninit<U>);
33
34impl<U: Copy> OptionULE<U> {
35    /// Obtain this as an `Option<T>`
36    pub fn get(self) -> Option<U> {
37        if self.0 {
38            unsafe {
39                // safety: self.0 is true so the MaybeUninit is valid
40                Some(self.1.assume_init())
41            }
42        } else {
43            None
44        }
45    }
46
47    /// Construct an `OptionULE<U>` from an equivalent `Option<T>`
48    pub fn new(opt: Option<U>) -> Self {
49        if let Some(inner) = opt {
50            Self(true, MaybeUninit::new(inner))
51        } else {
52            Self(false, MaybeUninit::zeroed())
53        }
54    }
55}
56
57impl<U: Copy + core::fmt::Debug> core::fmt::Debug for OptionULE<U> {
58    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
59        self.get().fmt(f)
60    }
61}
62
63// Safety (based on the safety checklist on the ULE trait):
64//  1. OptionULE does not include any uninitialized or padding bytes.
65//     (achieved by `#[repr(C, packed)]` on a struct containing only ULE fields,
66//     in the context of this impl. The MaybeUninit is valid for all byte sequences, and we only generate
67///    zeroed or valid-T byte sequences to fill it)
68//  2. OptionULE is aligned to 1 byte.
69//     (achieved by `#[repr(C, packed)]` on a struct containing only ULE fields, in the context of this impl)
70//  3. The impl of validate_byte_slice() returns an error if any byte is not valid.
71//  4. The impl of validate_byte_slice() returns an error if there are extra bytes.
72//  5. The other ULE methods use the default impl.
73//  6. OptionULE byte equality is semantic equality by relying on the ULE equality
74//     invariant on the subfields
75unsafe impl<U: ULE> ULE for OptionULE<U> {
76    fn validate_byte_slice(bytes: &[u8]) -> Result<(), ZeroVecError> {
77        let size = mem::size_of::<Self>();
78        if bytes.len() % size != 0 {
79            return Err(ZeroVecError::length::<Self>(bytes.len()));
80        }
81        for chunk in bytes.chunks(size) {
82            #[allow(clippy::indexing_slicing)] // `chunk` will have enough bytes to fit Self
83            match chunk[0] {
84                // https://doc.rust-lang.org/reference/types/boolean.html
85                // Rust booleans are always size 1, align 1 values with valid bit patterns 0x0 or 0x1
86                0 => {
87                    if !chunk[1..].iter().all(|x| *x == 0) {
88                        return Err(ZeroVecError::parse::<Self>());
89                    }
90                }
91                1 => U::validate_byte_slice(&chunk[1..])?,
92                _ => return Err(ZeroVecError::parse::<Self>()),
93            }
94        }
95        Ok(())
96    }
97}
98
99impl<T: AsULE> AsULE for Option<T> {
100    type ULE = OptionULE<T::ULE>;
101    fn to_unaligned(self) -> OptionULE<T::ULE> {
102        OptionULE::new(self.map(T::to_unaligned))
103    }
104
105    fn from_unaligned(other: OptionULE<T::ULE>) -> Self {
106        other.get().map(T::from_unaligned)
107    }
108}
109
110impl<U: Copy> Copy for OptionULE<U> {}
111
112impl<U: Copy> Clone for OptionULE<U> {
113    fn clone(&self) -> Self {
114        *self
115    }
116}
117
118impl<U: Copy + PartialEq> PartialEq for OptionULE<U> {
119    fn eq(&self, other: &Self) -> bool {
120        self.get().eq(&other.get())
121    }
122}
123
124impl<U: Copy + Eq> Eq for OptionULE<U> {}
125
126/// A type allowing one to represent `Option<U>` for [`VarULE`] `U` types.
127///
128/// ```rust
129/// use zerovec::ule::OptionVarULE;
130/// use zerovec::VarZeroVec;
131///
132/// let mut zv: VarZeroVec<OptionVarULE<str>> = VarZeroVec::new();
133///
134/// zv.make_mut().push(&None::<&str>);
135/// zv.make_mut().push(&Some("hello"));
136/// zv.make_mut().push(&Some("world"));
137/// zv.make_mut().push(&None::<&str>);
138///
139/// assert_eq!(zv.get(0).unwrap().as_ref(), None);
140/// assert_eq!(zv.get(1).unwrap().as_ref(), Some("hello"));
141/// ```
142// The slice field is empty when None (bool = false),
143// and is a valid T when Some (bool = true)
144#[repr(C, packed)]
145pub struct OptionVarULE<U: VarULE + ?Sized>(PhantomData<U>, bool, [u8]);
146
147impl<U: VarULE + ?Sized> OptionVarULE<U> {
148    /// Obtain this as an `Option<&U>`
149    pub fn as_ref(&self) -> Option<&U> {
150        if self.1 {
151            unsafe {
152                // Safety: byte field is a valid T if boolean field is true
153                Some(U::from_byte_slice_unchecked(&self.2))
154            }
155        } else {
156            None
157        }
158    }
159}
160
161impl<U: VarULE + ?Sized + core::fmt::Debug> core::fmt::Debug for OptionVarULE<U> {
162    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
163        self.as_ref().fmt(f)
164    }
165}
166
167// Safety (based on the safety checklist on the VarULE trait):
168//  1. OptionVarULE<T> does not include any uninitialized or padding bytes
169//     (achieved by being repr(C, packed) on ULE types)
170//  2. OptionVarULE<T> is aligned to 1 byte (achieved by being repr(C, packed) on ULE types)
171//  3. The impl of `validate_byte_slice()` returns an error if any byte is not valid.
172//  4. The impl of `validate_byte_slice()` returns an error if the slice cannot be used in its entirety
173//  5. The impl of `from_byte_slice_unchecked()` returns a reference to the same data.
174//  6. All other methods are defaulted
175//  7. OptionVarULE<T> byte equality is semantic equality (achieved by being an aggregate)
176unsafe impl<U: VarULE + ?Sized> VarULE for OptionVarULE<U> {
177    #[inline]
178    fn validate_byte_slice(slice: &[u8]) -> Result<(), ZeroVecError> {
179        if slice.is_empty() {
180            return Err(ZeroVecError::length::<Self>(slice.len()));
181        }
182        #[allow(clippy::indexing_slicing)] // slice already verified to be nonempty
183        match slice[0] {
184            // https://doc.rust-lang.org/reference/types/boolean.html
185            // Rust booleans are always size 1, align 1 values with valid bit patterns 0x0 or 0x1
186            0 => {
187                if slice.len() != 1 {
188                    Err(ZeroVecError::length::<Self>(slice.len()))
189                } else {
190                    Ok(())
191                }
192            }
193            1 => U::validate_byte_slice(&slice[1..]),
194            _ => Err(ZeroVecError::parse::<Self>()),
195        }
196    }
197
198    #[inline]
199    unsafe fn from_byte_slice_unchecked(bytes: &[u8]) -> &Self {
200        let entire_struct_as_slice: *const [u8] =
201            ::core::ptr::slice_from_raw_parts(bytes.as_ptr(), bytes.len() - 1);
202        &*(entire_struct_as_slice as *const Self)
203    }
204}
205
206unsafe impl<T, U> EncodeAsVarULE<OptionVarULE<U>> for Option<T>
207where
208    T: EncodeAsVarULE<U>,
209    U: VarULE + ?Sized,
210{
211    fn encode_var_ule_as_slices<R>(&self, _: impl FnOnce(&[&[u8]]) -> R) -> R {
212        // unnecessary if the other two are implemented
213        unreachable!()
214    }
215
216    #[inline]
217    fn encode_var_ule_len(&self) -> usize {
218        if let Some(ref inner) = *self {
219            // slice + boolean
220            1 + inner.encode_var_ule_len()
221        } else {
222            // boolean + empty slice
223            1
224        }
225    }
226
227    #[allow(clippy::indexing_slicing)] // This method is allowed to panic when lengths are invalid
228    fn encode_var_ule_write(&self, dst: &mut [u8]) {
229        if let Some(ref inner) = *self {
230            debug_assert!(
231                !dst.is_empty(),
232                "OptionVarULE must have at least one byte when Some"
233            );
234            dst[0] = 1;
235            inner.encode_var_ule_write(&mut dst[1..]);
236        } else {
237            debug_assert!(
238                dst.len() == 1,
239                "OptionVarULE must have exactly one byte when None"
240            );
241            dst[0] = 0;
242        }
243    }
244}
245
246impl<U: VarULE + ?Sized + PartialEq> PartialEq for OptionVarULE<U> {
247    fn eq(&self, other: &Self) -> bool {
248        self.as_ref().eq(&other.as_ref())
249    }
250}
251
252impl<U: VarULE + ?Sized + Eq> Eq for OptionVarULE<U> {}
253
254impl<U: VarULE + ?Sized + PartialOrd> PartialOrd for OptionVarULE<U> {
255    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
256        self.as_ref().partial_cmp(&other.as_ref())
257    }
258}
259
260impl<U: VarULE + ?Sized + Ord> Ord for OptionVarULE<U> {
261    fn cmp(&self, other: &Self) -> Ordering {
262        self.as_ref().cmp(&other.as_ref())
263    }
264}