Coverage Report

Created: 2025-10-10 06:28

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