Skip to main content

zerovec/ule/
vartuple.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
5//! Types to help compose fixed-size [`ULE`] and variable-size [`VarULE`] primitives.
6//!
7//! This module exports [`VarTuple`] and [`VarTupleULE`], which allow a single sized type and
8//! a single unsized type to be stored together as a [`VarULE`].
9//!
10//! # Examples
11//!
12//! ```
13//! use zerovec::ule::vartuple::{VarTuple, VarTupleULE};
14//! use zerovec::VarZeroVec;
15//!
16//! struct Employee<'a> {
17//!     id: u32,
18//!     name: &'a str,
19//! };
20//!
21//! let employees = [
22//!     Employee {
23//!         id: 12345,
24//!         name: "Jane Doe",
25//!     },
26//!     Employee {
27//!         id: 67890,
28//!         name: "John Doe",
29//!     },
30//! ];
31//!
32//! let employees_as_var_tuples = employees
33//!     .into_iter()
34//!     .map(|x| VarTuple {
35//!         sized: x.id,
36//!         variable: x.name,
37//!     })
38//!     .collect::<Vec<_>>();
39//!
40//! let employees_vzv: VarZeroVec<VarTupleULE<u32, str>> =
41//!     employees_as_var_tuples.as_slice().into();
42//!
43//! assert_eq!(employees_vzv.len(), 2);
44//!
45//! assert_eq!(employees_vzv.get(0).unwrap().sized.as_unsigned_int(), 12345);
46//! assert_eq!(&employees_vzv.get(0).unwrap().variable, "Jane Doe");
47//!
48//! assert_eq!(employees_vzv.get(1).unwrap().sized.as_unsigned_int(), 67890);
49//! assert_eq!(&employees_vzv.get(1).unwrap().variable, "John Doe");
50//! ```
51
52use core::mem::{size_of, transmute_copy};
53use zerofrom::ZeroFrom;
54
55#[cfg(feature = "alloc")]
56use alloc::{borrow::ToOwned, boxed::Box};
57
58use super::{AsULE, EncodeAsVarULE, UleError, VarULE, ULE};
59
60/// A sized type that can be converted to a [`VarTupleULE`].
61///
62/// See the module for examples.
63#[derive(#[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl<A: ::core::fmt::Debug, B: ::core::fmt::Debug> ::core::fmt::Debug for
    VarTuple<A, B> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f, "VarTuple",
            "sized", &self.sized, "variable", &&self.variable)
    }
}Debug, #[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl<A: ::core::cmp::PartialEq, B: ::core::cmp::PartialEq>
    ::core::cmp::PartialEq for VarTuple<A, B> {
    #[inline]
    fn eq(&self, other: &VarTuple<A, B>) -> bool {
        self.sized == other.sized && self.variable == other.variable
    }
}PartialEq, #[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl<A: ::core::cmp::Eq, B: ::core::cmp::Eq> ::core::cmp::Eq for
    VarTuple<A, B> {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<A>;
        let _: ::core::cmp::AssertParamIsEq<B>;
    }
}Eq, #[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl<A: ::core::cmp::PartialOrd, B: ::core::cmp::PartialOrd>
    ::core::cmp::PartialOrd for VarTuple<A, B> {
    #[inline]
    fn partial_cmp(&self, other: &VarTuple<A, B>)
        -> ::core::option::Option<::core::cmp::Ordering> {
        match ::core::cmp::PartialOrd::partial_cmp(&self.sized, &other.sized)
            {
            ::core::option::Option::Some(::core::cmp::Ordering::Equal) =>
                ::core::cmp::PartialOrd::partial_cmp(&self.variable,
                    &other.variable),
            cmp => cmp,
        }
    }
}PartialOrd, #[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl<A: ::core::cmp::Ord, B: ::core::cmp::Ord> ::core::cmp::Ord for
    VarTuple<A, B> {
    #[inline]
    fn cmp(&self, other: &VarTuple<A, B>) -> ::core::cmp::Ordering {
        match ::core::cmp::Ord::cmp(&self.sized, &other.sized) {
            ::core::cmp::Ordering::Equal =>
                ::core::cmp::Ord::cmp(&self.variable, &other.variable),
            cmp => cmp,
        }
    }
}Ord, #[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl<A: ::core::clone::Clone, B: ::core::clone::Clone> ::core::clone::Clone
    for VarTuple<A, B> {
    #[inline]
    fn clone(&self) -> VarTuple<A, B> {
        VarTuple {
            sized: ::core::clone::Clone::clone(&self.sized),
            variable: ::core::clone::Clone::clone(&self.variable),
        }
    }
}Clone)]
64#[allow(clippy::exhaustive_structs)] // well-defined type
65#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
66pub struct VarTuple<A, B> {
67    pub sized: A,
68    pub variable: B,
69}
70
71/// A dynamically-sized type combining a sized and an unsized type.
72///
73/// See the module for examples.
74#[derive(#[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl<A: ::core::fmt::Debug + AsULE, V: ::core::fmt::Debug + VarULE + ?Sized>
    ::core::fmt::Debug for VarTupleULE<A, V> where A::ULE: ::core::fmt::Debug
    {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f, "VarTupleULE",
            "sized", &self.sized, "variable", &&self.variable)
    }
}Debug, #[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl<A: ::core::cmp::PartialEq + AsULE, V: ::core::cmp::PartialEq + VarULE +
    ?Sized> ::core::cmp::PartialEq for VarTupleULE<A, V> where
    A::ULE: ::core::cmp::PartialEq {
    #[inline]
    fn eq(&self, other: &VarTupleULE<A, V>) -> bool {
        self.sized == other.sized && self.variable == other.variable
    }
}PartialEq, #[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl<A: ::core::cmp::Eq + AsULE, V: ::core::cmp::Eq + VarULE + ?Sized>
    ::core::cmp::Eq for VarTupleULE<A, V> where A::ULE: ::core::cmp::Eq {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<A::ULE>;
        let _: ::core::cmp::AssertParamIsEq<V>;
    }
}Eq, #[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl<A: ::core::cmp::PartialOrd + AsULE, V: ::core::cmp::PartialOrd + VarULE +
    ?Sized> ::core::cmp::PartialOrd for VarTupleULE<A, V> where
    A::ULE: ::core::cmp::PartialOrd {
    #[inline]
    fn partial_cmp(&self, other: &VarTupleULE<A, V>)
        -> ::core::option::Option<::core::cmp::Ordering> {
        match ::core::cmp::PartialOrd::partial_cmp(&self.sized, &other.sized)
            {
            ::core::option::Option::Some(::core::cmp::Ordering::Equal) =>
                ::core::cmp::PartialOrd::partial_cmp(&self.variable,
                    &other.variable),
            cmp => cmp,
        }
    }
}PartialOrd, #[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl<A: ::core::cmp::Ord + AsULE, V: ::core::cmp::Ord + VarULE + ?Sized>
    ::core::cmp::Ord for VarTupleULE<A, V> where A::ULE: ::core::cmp::Ord {
    #[inline]
    fn cmp(&self, other: &VarTupleULE<A, V>) -> ::core::cmp::Ordering {
        match ::core::cmp::Ord::cmp(&self.sized, &other.sized) {
            ::core::cmp::Ordering::Equal =>
                ::core::cmp::Ord::cmp(&self.variable, &other.variable),
            cmp => cmp,
        }
    }
}Ord)]
75#[allow(clippy::exhaustive_structs)] // well-defined type
76#[repr(C)]
77pub struct VarTupleULE<A: AsULE, V: VarULE + ?Sized> {
78    pub sized: A::ULE,
79    pub variable: V,
80}
81
82// # Safety
83//
84// ## Representation
85//
86// The type `VarTupleULE` is align(1) because it is repr(C) and its fields
87// are all align(1), since they are themselves ULE and VarULE, which have
88// this same safety constraint. Further, there is no padding, because repr(C)
89// does not add padding when all fields are align(1).
90//
91// <https://doc.rust-lang.org/reference/type-layout.html#the-c-representation>
92//
93// Pointers to `VarTupleULE` are fat pointers with metadata equal to the
94// metadata of the inner DST field V.
95//
96// <https://doc.rust-lang.org/stable/std/ptr/trait.Pointee.html>
97//
98// ## Checklist
99//
100// Safety checklist for `VarULE`:
101//
102// 1. align(1): see "Representation" above.
103// 2. No padding: see "Representation" above.
104// 3. `validate_bytes` checks length and defers to the inner ULEs.
105// 4. `validate_bytes` checks length and defers to the inner ULEs.
106// 5. `from_bytes_unchecked` returns a fat pointer to the bytes.
107// 6. All other methods are left at their default impl.
108// 7. The two ULEs have byte equality, so this composition has byte equality.
109unsafe impl<A, V> VarULE for VarTupleULE<A, V>
110where
111    A: AsULE + 'static,
112    V: VarULE + ?Sized,
113{
114    fn validate_bytes(bytes: &[u8]) -> Result<(), UleError> {
115        let (sized_chunk, variable_chunk) = bytes
116            .split_at_checked(size_of::<A::ULE>())
117            .ok_or_else(|| UleError::length::<Self>(bytes.len()))?;
118        A::ULE::validate_bytes(sized_chunk)?;
119        V::validate_bytes(variable_chunk)?;
120        Ok(())
121    }
122
123    unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Self {
124        let (_sized_chunk, variable_chunk) = bytes.split_at_unchecked(size_of::<A::ULE>());
125        // Safety: variable_chunk is a valid V because of this function's precondition: bytes is a valid Self,
126        // and a valid Self contains a valid V after the space needed for A::ULE.
127        let variable_ref = V::from_bytes_unchecked(variable_chunk);
128        let variable_ptr: *const V = variable_ref;
129
130        // Safety: The DST of VarTupleULE is a pointer to the `sized` element and has a metadata
131        // equal to the metadata of the `variable` field (see "Representation" comments on the impl).
132
133        // We should use the pointer metadata APIs here when they are stable: https://github.com/rust-lang/rust/issues/81513
134        // For now we rely on all DST metadata being a usize.
135
136        // Extract metadata from V's DST
137        // Rust doesn't know that `&V` is a fat pointer so we have to use transmute_copy
138        match (&size_of::<*const V>(), &size_of::<(*const u8, usize)>()) {
    (left_val, right_val) => {
        if !(*left_val == *right_val) {
            let kind = ::core::panicking::AssertKind::Eq;
            ::core::panicking::assert_failed(kind, &*left_val, &*right_val,
                ::core::option::Option::None);
        }
    }
};assert_eq!(size_of::<*const V>(), size_of::<(*const u8, usize)>());
139        // Safety: We have asserted that the transmute Src and Dst are the same size. Furthermore,
140        // DST pointers are a pointer and usize length metadata
141        let (_v_ptr, metadata) = transmute_copy::<*const V, (*const u8, usize)>(&variable_ptr);
142
143        // Construct a new DST with the same metadata as V
144        match (&size_of::<*const Self>(), &size_of::<(*const u8, usize)>()) {
    (left_val, right_val) => {
        if !(*left_val == *right_val) {
            let kind = ::core::panicking::AssertKind::Eq;
            ::core::panicking::assert_failed(kind, &*left_val, &*right_val,
                ::core::option::Option::None);
        }
    }
};assert_eq!(size_of::<*const Self>(), size_of::<(*const u8, usize)>());
145        // Safety: Same as above but in the other direction.
146        let composed_ptr =
147            transmute_copy::<(*const u8, usize), *const Self>(&(bytes.as_ptr(), metadata));
148        &*(composed_ptr)
149    }
150}
151
152// # Safety
153//
154// encode_var_ule_len: returns the length of the two ULEs together.
155//
156// encode_var_ule_write: writes bytes by deferring to the inner ULE impls.
157unsafe impl<A, B, V> EncodeAsVarULE<VarTupleULE<A, V>> for VarTuple<A, B>
158where
159    A: AsULE + 'static,
160    B: EncodeAsVarULE<V>,
161    V: VarULE + ?Sized,
162{
163    fn encode_var_ule_as_slices<R>(&self, _: impl FnOnce(&[&[u8]]) -> R) -> R {
164        // unnecessary if the other two are implemented
165        ::core::panicking::panic("internal error: entered unreachable code")unreachable!()
166    }
167
168    #[inline]
169    fn encode_var_ule_len(&self) -> usize {
170        size_of::<A::ULE>() + self.variable.encode_var_ule_len()
171    }
172
173    #[inline]
174    fn encode_var_ule_write(&self, dst: &mut [u8]) {
175        // TODO: use split_first_chunk_mut in 1.77
176        let (sized_chunk, variable_chunk) = dst.split_at_mut(size_of::<A::ULE>());
177        sized_chunk.clone_from_slice([self.sized.to_unaligned()].as_bytes());
178        self.variable.encode_var_ule_write(variable_chunk);
179    }
180}
181
182#[cfg(feature = "alloc")]
183impl<A, V> ToOwned for VarTupleULE<A, V>
184where
185    A: AsULE + 'static,
186    V: VarULE + ?Sized,
187{
188    type Owned = Box<Self>;
189    fn to_owned(&self) -> Self::Owned {
190        crate::ule::encode_varule_to_box(self)
191    }
192}
193
194impl<'a, A, B, V> ZeroFrom<'a, VarTupleULE<A, V>> for VarTuple<A, B>
195where
196    A: AsULE + 'static,
197    V: VarULE + ?Sized,
198    B: ZeroFrom<'a, V>,
199{
200    fn zero_from(other: &'a VarTupleULE<A, V>) -> Self {
201        VarTuple {
202            sized: AsULE::from_unaligned(other.sized),
203            variable: B::zero_from(&other.variable),
204        }
205    }
206}
207
208#[cfg(feature = "serde")]
209impl<A, V> serde::Serialize for VarTupleULE<A, V>
210where
211    A: AsULE + 'static,
212    V: VarULE + ?Sized,
213    A: serde::Serialize,
214    V: serde::Serialize,
215{
216    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
217    where
218        S: serde::Serializer,
219    {
220        if serializer.is_human_readable() {
221            let this = VarTuple {
222                sized: A::from_unaligned(self.sized),
223                variable: &self.variable,
224            };
225            this.serialize(serializer)
226        } else {
227            serializer.serialize_bytes(self.as_bytes())
228        }
229    }
230}
231
232#[cfg(feature = "serde")]
233impl<'a, 'de: 'a, A, V> serde::Deserialize<'de> for &'a VarTupleULE<A, V>
234where
235    A: AsULE + 'static,
236    V: VarULE + ?Sized,
237    A: serde::Deserialize<'de>,
238{
239    fn deserialize<Des>(deserializer: Des) -> Result<Self, Des::Error>
240    where
241        Des: serde::Deserializer<'de>,
242    {
243        if !deserializer.is_human_readable() {
244            let bytes = <&[u8]>::deserialize(deserializer)?;
245            VarTupleULE::<A, V>::parse_bytes(bytes).map_err(serde::de::Error::custom)
246        } else {
247            Err(serde::de::Error::custom(
248                "&VarTupleULE can only deserialize in zero-copy ways",
249            ))
250        }
251    }
252}
253
254#[cfg(all(feature = "serde", feature = "alloc"))]
255impl<'de, A, V> serde::Deserialize<'de> for Box<VarTupleULE<A, V>>
256where
257    A: AsULE + 'static,
258    V: VarULE + ?Sized,
259    A: serde::Deserialize<'de>,
260    Box<V>: serde::Deserialize<'de>,
261{
262    fn deserialize<Des>(deserializer: Des) -> Result<Self, Des::Error>
263    where
264        Des: serde::Deserializer<'de>,
265    {
266        if deserializer.is_human_readable() {
267            let this = VarTuple::<A, Box<V>>::deserialize(deserializer)?;
268            Ok(crate::ule::encode_varule_to_box(&this))
269        } else {
270            // This branch should usually not be hit, since Cow-like use cases will hit the Deserialize impl for &'a TupleNVarULE instead.
271
272            let deserialized = <&VarTupleULE<A, V>>::deserialize(deserializer)?;
273            Ok(deserialized.to_boxed())
274        }
275    }
276}
277
278#[test]
279fn test_simple() {
280    let var_tuple = VarTuple {
281        sized: 1500u16,
282        variable: "hello",
283    };
284    let var_tuple_ule = super::encode_varule_to_box(&var_tuple);
285    assert_eq!(var_tuple_ule.sized.as_unsigned_int(), 1500);
286    assert_eq!(&var_tuple_ule.variable, "hello");
287
288    // Can't use inference due to https://github.com/rust-lang/rust/issues/130180
289    #[cfg(feature = "serde")]
290    crate::ule::test_utils::assert_serde_roundtrips::<VarTupleULE<u16, str>>(&var_tuple_ule);
291}
292
293#[test]
294fn test_nested() {
295    use crate::{ZeroSlice, ZeroVec};
296    let var_tuple = VarTuple {
297        sized: 2000u16,
298        variable: VarTuple {
299            sized: '🦙',
300            variable: ZeroVec::alloc_from_slice(b"ICU"),
301        },
302    };
303    let var_tuple_ule = super::encode_varule_to_box(&var_tuple);
304    assert_eq!(var_tuple_ule.sized.as_unsigned_int(), 2000u16);
305    assert_eq!(var_tuple_ule.variable.sized.to_char(), '🦙');
306    assert_eq!(
307        &var_tuple_ule.variable.variable,
308        ZeroSlice::from_ule_slice(b"ICU")
309    );
310    // Can't use inference due to https://github.com/rust-lang/rust/issues/130180
311    #[cfg(feature = "serde")]
312    crate::ule::test_utils::assert_serde_roundtrips::<
313        VarTupleULE<u16, VarTupleULE<char, ZeroSlice<_>>>,
314    >(&var_tuple_ule);
315}