| 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 | |
| 5 | use super::*; |
| 6 | use core::cmp::Ordering; |
| 7 | use core::marker::PhantomData; |
| 8 | use 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)] |
| 32 | pub struct OptionULE<U>(bool, MaybeUninit<U>); |
| 33 | |
| 34 | impl<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: U) = opt { |
| 50 | Self(true, MaybeUninit::new(val:inner)) |
| 51 | } else { |
| 52 | Self(false, MaybeUninit::zeroed()) |
| 53 | } |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | impl<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 |
| 75 | unsafe impl<U: ULE> ULE for OptionULE<U> { |
| 76 | fn validate_byte_slice(bytes: &[u8]) -> Result<(), ZeroVecError> { |
| 77 | let size: usize = mem::size_of::<Self>(); |
| 78 | if bytes.len() % size != 0 { |
| 79 | return Err(ZeroVecError::length::<Self>(bytes.len())); |
| 80 | } |
| 81 | for chunk: &[u8] 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: &u8| *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 | |
| 99 | impl<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 | |
| 110 | impl<U: Copy> Copy for OptionULE<U> {} |
| 111 | |
| 112 | impl<U: Copy> Clone for OptionULE<U> { |
| 113 | fn clone(&self) -> Self { |
| 114 | *self |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | impl<U: Copy + PartialEq> PartialEq for OptionULE<U> { |
| 119 | fn eq(&self, other: &Self) -> bool { |
| 120 | self.get().eq(&other.get()) |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | impl<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)] |
| 145 | pub struct OptionVarULE<U: VarULE + ?Sized>(PhantomData<U>, bool, [u8]); |
| 146 | |
| 147 | impl<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 | |
| 161 | impl<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) |
| 176 | unsafe 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 | |
| 206 | unsafe impl<T, U> EncodeAsVarULE<OptionVarULE<U>> for Option<T> |
| 207 | where |
| 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 | |
| 246 | impl<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 | |
| 252 | impl<U: VarULE + ?Sized + Eq> Eq for OptionVarULE<U> {} |
| 253 | |
| 254 | impl<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 | |
| 260 | impl<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 | } |
| 265 | |