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 core::{marker::Copy, mem::size_of};
6
7use 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`].
20pub 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)]
50pub 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
57impl<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
65impl<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
90impl<U: NicheBytes<N> + ULE, const N: usize> Copy for NichedOptionULE<U, N> {}
91
92impl<U: NicheBytes<N> + ULE, const N: usize> Clone for NichedOptionULE<U, N> {
93 fn clone(&self) -> Self {
94 *self
95 }
96}
97
98impl<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
104impl<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(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).
118unsafe 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(chunk_size: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]
147pub struct NichedOption<U, const N: usize>(pub Option<U>);
148
149impl<U, const N: usize> NichedOption<U, N> {
150 pub const fn new(o: Option<U>) -> Self {
151 Self(o)
152 }
153}
154
155impl<U, const N: usize> Default for NichedOption<U, N> {
156 fn default() -> Self {
157 Self(None)
158 }
159}
160
161impl<U, const N: usize> From<Option<U>> for NichedOption<U, N> {
162 fn from(o: Option<U>) -> Self {
163 Self(o)
164 }
165}
166
167impl<U: AsULE, const N: usize> AsULE for NichedOption<U, N>
168where
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