// This file is part of ICU4X. For terms of use, please see the file // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). use super::*; use core::cmp::Ordering; use core::marker::PhantomData; use core::mem::{self, MaybeUninit}; /// This type is the [`ULE`] type for `Option` where `U` is a [`ULE`] type /// /// # Example /// /// ```rust /// use zerovec::ZeroVec; /// /// let z = ZeroVec::alloc_from_slice(&[ /// Some('a'), /// Some('á'), /// Some('ø'), /// None, /// Some('ł'), /// ]); /// /// assert_eq!(z.get(2), Some(Some(('ø')))); /// assert_eq!(z.get(3), Some(None)); /// ``` // Invariants: // The MaybeUninit is zeroed when None (bool = false), // and is valid when Some (bool = true) #[repr(packed)] pub struct OptionULE(bool, MaybeUninit); impl OptionULE { /// Obtain this as an Option pub fn get(self) -> Option { if self.0 { unsafe { // safety: self.0 is true so the MaybeUninit is valid Some(self.1.assume_init()) } } else { None } } /// Construct an OptionULE from an equivalent Option pub fn new(opt: Option) -> Self { if let Some(inner) = opt { Self(true, MaybeUninit::new(inner)) } else { Self(false, MaybeUninit::zeroed()) } } } // Safety (based on the safety checklist on the ULE trait): // 1. OptionULE does not include any uninitialized or padding bytes. // (achieved by `#[repr(packed)]` on a struct containing only ULE fields, // in the context of this impl. The MaybeUninit is valid for all byte sequences, and we only generate /// zeroed or valid-T byte sequences to fill it) // 2. OptionULE is aligned to 1 byte. // (achieved by `#[repr(packed)]` on a struct containing only ULE fields, in the context of this impl) // 3. The impl of validate_byte_slice() returns an error if any byte is not valid. // 4. The impl of validate_byte_slice() returns an error if there are extra bytes. // 5. The other ULE methods use the default impl. // 6. OptionULE byte equality is semantic equality by relying on the ULE equality // invariant on the subfields unsafe impl ULE for OptionULE { fn validate_byte_slice(bytes: &[u8]) -> Result<(), ZeroVecError> { let size = mem::size_of::(); if bytes.len() % size != 0 { return Err(ZeroVecError::length::(bytes.len())); } for chunk in bytes.chunks(size) { #[allow(clippy::indexing_slicing)] // `chunk` will have enough bytes to fit Self match chunk[0] { // https://doc.rust-lang.org/reference/types/boolean.html // Rust booleans are always size 1, align 1 values with valid bit patterns 0x0 or 0x1 0 => { if !chunk[1..].iter().all(|x| *x == 0) { return Err(ZeroVecError::parse::()); } } 1 => U::validate_byte_slice(&chunk[1..])?, _ => return Err(ZeroVecError::parse::()), } } Ok(()) } } impl AsULE for Option { type ULE = OptionULE; fn to_unaligned(self) -> OptionULE { OptionULE::new(self.map(T::to_unaligned)) } fn from_unaligned(other: OptionULE) -> Self { other.get().map(T::from_unaligned) } } impl Copy for OptionULE {} impl Clone for OptionULE { fn clone(&self) -> Self { *self } } impl PartialEq for OptionULE { fn eq(&self, other: &Self) -> bool { self.get().eq(&other.get()) } } impl Eq for OptionULE {} /// A type allowing one to represent `Option` for [`VarULE`] `U` types. /// /// ```rust /// use zerovec::ule::OptionVarULE; /// use zerovec::VarZeroVec; /// /// let mut zv: VarZeroVec> = VarZeroVec::new(); /// /// zv.make_mut().push(&None::<&str>); /// zv.make_mut().push(&Some("hello")); /// zv.make_mut().push(&Some("world")); /// zv.make_mut().push(&None::<&str>); /// /// assert_eq!(zv.get(0).unwrap().as_ref(), None); /// assert_eq!(zv.get(1).unwrap().as_ref(), Some("hello")); /// ``` // The slice field is empty when None (bool = false), // and is a valid T when Some (bool = true) #[repr(packed)] pub struct OptionVarULE(PhantomData, bool, [u8]); impl OptionVarULE { /// Obtain this as an `Option<&U>` pub fn as_ref(&self) -> Option<&U> { if self.1 { unsafe { // Safety: byte field is a valid T if boolean field is true Some(U::from_byte_slice_unchecked(&self.2)) } } else { None } } } // Safety (based on the safety checklist on the VarULE trait): // 1. OptionVarULE does not include any uninitialized or padding bytes // (achieved by being repr(packed) on ULE types) // 2. OptionVarULE is aligned to 1 byte (achieved by being repr(packed) on ULE types) // 3. The impl of `validate_byte_slice()` returns an error if any byte is not valid. // 4. The impl of `validate_byte_slice()` returns an error if the slice cannot be used in its entirety // 5. The impl of `from_byte_slice_unchecked()` returns a reference to the same data. // 6. All other methods are defaulted // 7. OptionVarULE byte equality is semantic equality (achieved by being an aggregate) unsafe impl VarULE for OptionVarULE { #[inline] fn validate_byte_slice(slice: &[u8]) -> Result<(), ZeroVecError> { if slice.is_empty() { return Err(ZeroVecError::length::(slice.len())); } #[allow(clippy::indexing_slicing)] // slice already verified to be nonempty match slice[0] { // https://doc.rust-lang.org/reference/types/boolean.html // Rust booleans are always size 1, align 1 values with valid bit patterns 0x0 or 0x1 0 => { if slice.len() != 1 { Err(ZeroVecError::length::(slice.len())) } else { Ok(()) } } 1 => U::validate_byte_slice(&slice[1..]), _ => Err(ZeroVecError::parse::()), } } #[inline] unsafe fn from_byte_slice_unchecked(bytes: &[u8]) -> &Self { let metadata = bytes.len() - 1; let entire_struct_as_slice: *const [u8] = ::core::slice::from_raw_parts(bytes.as_ptr(), metadata); &*(entire_struct_as_slice as *const Self) } } unsafe impl EncodeAsVarULE> for Option where T: EncodeAsVarULE, U: VarULE + ?Sized, { fn encode_var_ule_as_slices(&self, _: impl FnOnce(&[&[u8]]) -> R) -> R { // unnecessary if the other two are implemented unreachable!() } #[inline] fn encode_var_ule_len(&self) -> usize { if let Some(ref inner) = *self { // slice + boolean 1 + inner.encode_var_ule_len() } else { // boolean + empty slice 1 } } #[allow(clippy::indexing_slicing)] // This method is allowed to panic when lengths are invalid fn encode_var_ule_write(&self, dst: &mut [u8]) { if let Some(ref inner) = *self { debug_assert!( !dst.is_empty(), "OptionVarULE must have at least one byte when Some" ); dst[0] = 1; inner.encode_var_ule_write(&mut dst[1..]); } else { debug_assert!( dst.len() == 1, "OptionVarULE must have exactly one byte when None" ); dst[0] = 0; } } } impl PartialEq for OptionVarULE { fn eq(&self, other: &Self) -> bool { self.as_ref().eq(&other.as_ref()) } } impl Eq for OptionVarULE {} impl PartialOrd for OptionVarULE { fn partial_cmp(&self, other: &Self) -> Option { self.as_ref().partial_cmp(&other.as_ref()) } } impl Ord for OptionVarULE { fn cmp(&self, other: &Self) -> Ordering { self.as_ref().cmp(&other.as_ref()) } }