| 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 core::{marker::Copy, mem::size_of}; |
| 6 | |
| 7 | use super::{AsULE, ULE}; |
| 8 | |
| 9 | /// The [`ULE`] types implementing this trait guarantee that [`NicheBytes::NICHE_BIT_PATTERN`] |
| 10 | /// can never occur as a valid byte representation of the type. |
| 11 | /// |
| 12 | /// Guarantees for a valid implementation. |
| 13 | /// 1. N must be equal to `core::mem::sizeo_of::<Self>()` or else it will |
| 14 | /// cause panics. |
| 15 | /// 2. The bit pattern [`NicheBytes::NICHE_BIT_PATTERN`] must not be incorrect as it would lead to |
| 16 | /// weird behaviour. |
| 17 | /// 3. The abstractions built on top of this trait must panic on an invalid N. |
| 18 | /// 4. The abstractions built on this trait that use type punning must ensure that type being |
| 19 | /// punned is [`ULE`]. |
| 20 | pub trait NicheBytes<const N: usize> { |
| 21 | const NICHE_BIT_PATTERN: [u8; N]; |
| 22 | } |
| 23 | |
| 24 | /// [`ULE`] type for [`NichedOption<U,N>`] where U implements [`NicheBytes`]. |
| 25 | /// The invalid bit pattern is used as the niche. |
| 26 | /// |
| 27 | /// This uses 1 byte less than [`crate::ule::OptionULE<U>`] to represent [`NichedOption<U,N>`]. |
| 28 | /// |
| 29 | /// # Example |
| 30 | /// |
| 31 | /// ``` |
| 32 | /// use core::num::NonZeroI8; |
| 33 | /// use zerovec::ule::NichedOption; |
| 34 | /// use zerovec::ZeroVec; |
| 35 | /// |
| 36 | /// let bytes = &[0x00, 0x01, 0x02, 0x00]; |
| 37 | /// let zv_no: ZeroVec<NichedOption<NonZeroI8, 1>> = |
| 38 | /// ZeroVec::parse_byte_slice(bytes) |
| 39 | /// .expect("Unable to parse as NichedOption." ); |
| 40 | /// |
| 41 | /// assert_eq!(zv_no.get(0).map(|e| e.0), Some(None)); |
| 42 | /// assert_eq!(zv_no.get(1).map(|e| e.0), Some(NonZeroI8::new(1))); |
| 43 | /// assert_eq!(zv_no.get(2).map(|e| e.0), Some(NonZeroI8::new(2))); |
| 44 | /// assert_eq!(zv_no.get(3).map(|e| e.0), Some(None)); |
| 45 | /// ``` |
| 46 | // Invariants: |
| 47 | // The union stores [`NicheBytes::NICHE_BIT_PATTERN`] when None. |
| 48 | // Any other bit pattern is a valid. |
| 49 | #[repr (C)] |
| 50 | pub union NichedOptionULE<U: NicheBytes<N> + ULE, const N: usize> { |
| 51 | /// Invariant: The value is `niche` only if the bytes equal NICHE_BIT_PATTERN. |
| 52 | niche: [u8; N], |
| 53 | /// Invariant: The value is `valid` if the `niche` field does not match NICHE_BIT_PATTERN. |
| 54 | valid: U, |
| 55 | } |
| 56 | |
| 57 | impl<U: NicheBytes<N> + ULE + core::fmt::Debug, const N: usize> core::fmt::Debug |
| 58 | for NichedOptionULE<U, N> |
| 59 | { |
| 60 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
| 61 | self.get().fmt(f) |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | impl<U: NicheBytes<N> + ULE, const N: usize> NichedOptionULE<U, N> { |
| 66 | /// New `NichedOptionULE<U, N>` from `Option<U>` |
| 67 | pub fn new(opt: Option<U>) -> Self { |
| 68 | assert!(N == core::mem::size_of::<U>()); |
| 69 | match opt { |
| 70 | Some(u: U) => Self { valid: u }, |
| 71 | None => Self { |
| 72 | niche: <U as NicheBytes<N>>::NICHE_BIT_PATTERN, |
| 73 | }, |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | /// Convert to an `Option<U>` |
| 78 | pub fn get(self) -> Option<U> { |
| 79 | // Safety: The union stores NICHE_BIT_PATTERN when None otherwise a valid U |
| 80 | unsafe { |
| 81 | if self.niche == <U as NicheBytes<N>>::NICHE_BIT_PATTERN { |
| 82 | None |
| 83 | } else { |
| 84 | Some(self.valid) |
| 85 | } |
| 86 | } |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | impl<U: NicheBytes<N> + ULE, const N: usize> Copy for NichedOptionULE<U, N> {} |
| 91 | |
| 92 | impl<U: NicheBytes<N> + ULE, const N: usize> Clone for NichedOptionULE<U, N> { |
| 93 | fn clone(&self) -> Self { |
| 94 | *self |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | impl<U: NicheBytes<N> + ULE + PartialEq, const N: usize> PartialEq for NichedOptionULE<U, N> { |
| 99 | fn eq(&self, other: &Self) -> bool { |
| 100 | self.get().eq(&other.get()) |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | impl<U: NicheBytes<N> + ULE + Eq, const N: usize> Eq for NichedOptionULE<U, N> {} |
| 105 | |
| 106 | /// Safety for ULE trait |
| 107 | /// 1. NichedOptionULE does not have any padding bytes due to `#[repr(C)]` on a struct |
| 108 | /// containing only ULE fields. |
| 109 | /// NichedOptionULE either contains NICHE_BIT_PATTERN or valid U byte sequences. |
| 110 | /// In both cases the data is initialized. |
| 111 | /// 2. NichedOptionULE is aligned to 1 byte due to `#[repr(C, packed)]` on a struct containing only |
| 112 | /// ULE fields. |
| 113 | /// 3. validate_byte_slice impl returns an error if invalid bytes are encountered. |
| 114 | /// 4. validate_byte_slice impl returns an error there are extra bytes. |
| 115 | /// 5. The other ULE methods are left to their default impl. |
| 116 | /// 6. NichedOptionULE equality is based on ULE equality of the subfield, assuming that NicheBytes |
| 117 | /// has been implemented correctly (this is a correctness but not a safety guarantee). |
| 118 | unsafe impl<U: NicheBytes<N> + ULE, const N: usize> ULE for NichedOptionULE<U, N> { |
| 119 | fn validate_byte_slice(bytes: &[u8]) -> Result<(), crate::ZeroVecError> { |
| 120 | let size: usize = size_of::<Self>(); |
| 121 | // The implemention is only correct if NICHE_BIT_PATTERN has same number of bytes as the |
| 122 | // type. |
| 123 | debug_assert!(N == core::mem::size_of::<U>()); |
| 124 | |
| 125 | // The bytes should fully transmute to a collection of Self |
| 126 | if bytes.len() % size != 0 { |
| 127 | return Err(crate::ZeroVecError::length::<Self>(bytes.len())); |
| 128 | } |
| 129 | bytes.chunks(size).try_for_each(|chunk: &[u8]| { |
| 130 | // Associated const cannot be referenced in a pattern |
| 131 | // https://doc.rust-lang.org/error-index.html#E0158 |
| 132 | if chunk == <U as NicheBytes<N>>::NICHE_BIT_PATTERN { |
| 133 | Ok(()) |
| 134 | } else { |
| 135 | U::validate_byte_slice(bytes:chunk) |
| 136 | } |
| 137 | }) |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | /// Optional type which uses [`NichedOptionULE<U,N>`] as ULE type. |
| 142 | /// The implementors guarantee that `N == core::mem::sizeo_of::<Self>()` |
| 143 | /// [`repr(transparent)`] guarantees that the layout is same as [`Option<U>`] |
| 144 | #[derive (Debug, Copy, Clone, PartialEq, Eq, Hash)] |
| 145 | #[repr (transparent)] |
| 146 | #[non_exhaustive ] |
| 147 | pub struct NichedOption<U, const N: usize>(pub Option<U>); |
| 148 | |
| 149 | impl<U, const N: usize> NichedOption<U, N> { |
| 150 | pub const fn new(o: Option<U>) -> Self { |
| 151 | Self(o) |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | impl<U, const N: usize> Default for NichedOption<U, N> { |
| 156 | fn default() -> Self { |
| 157 | Self(None) |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | impl<U, const N: usize> From<Option<U>> for NichedOption<U, N> { |
| 162 | fn from(o: Option<U>) -> Self { |
| 163 | Self(o) |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | impl<U: AsULE, const N: usize> AsULE for NichedOption<U, N> |
| 168 | where |
| 169 | U::ULE: NicheBytes<N>, |
| 170 | { |
| 171 | type ULE = NichedOptionULE<U::ULE, N>; |
| 172 | |
| 173 | fn to_unaligned(self) -> Self::ULE { |
| 174 | NichedOptionULE::new(self.0.map(U::to_unaligned)) |
| 175 | } |
| 176 | |
| 177 | fn from_unaligned(unaligned: Self::ULE) -> Self { |
| 178 | Self(unaligned.get().map(U::from_unaligned)) |
| 179 | } |
| 180 | } |
| 181 | |