Skip to main content

generic_array/ext_impls/
impl_rkyv_0_8.rs

1// The `Archive`, `Serialize` and `Deserialize` impls below mirror rkyv's own impls for
2// `[T; N]` (`rkyv-0.8/src/impls/core/mod.rs`) 1:1. In particular, they share the same
3// behavior on a failed element-wise serialize/deserialize: previously-written entries in
4// the resolver/result `MaybeUninit` are leaked rather than dropped. For typical rkyv
5// `Resolver`s (often `()`) and `Copy` element types this is a non-issue; staying in sync
6// with upstream is preferable to diverging here.
7
8use core::mem;
9
10use rkyv_0_8::{
11    rancor::Fallible,
12    traits::{CopyOptimization, NoUndef},
13    Archive, Deserialize, Place, Portable, Serialize,
14};
15
16use crate::{ArrayLength, GenericArray};
17
18// SAFETY: `GenericArray<T, N>` is a `T` array and so is portable as long as `T` is also
19// `Portable`.
20unsafe impl<T: Portable, N: ArrayLength> Portable for GenericArray<T, N> {}
21// SAFETY: `GenericArray<T, N>` is a `T` array and so has no uninitialized bytes as long as
22// `T` also has no uninitialized bytes.
23unsafe impl<T: NoUndef, N: ArrayLength> NoUndef for GenericArray<T, N> {}
24
25/// Gets a `Place` to the `i`-th element of the array.
26///
27/// # Safety
28///
29/// `i` must be in-bounds for the array pointed to by this place.
30///
31/// This is a 1:1 copy of [`Place<[T; N]>::index`]
32#[inline]
33unsafe fn index_for_place_generic_array<T, N: ArrayLength>(
34    place: Place<GenericArray<T, N>>,
35    i: usize,
36) -> Place<T> {
37    // SAFETY: The caller has guaranteed that `i` is in-bounds for the array
38    // pointed to by this place.
39    let ptr = unsafe { place.ptr().cast::<T>().add(i) };
40    // SAFETY: `ptr` is an element of `self`, and so is also properly
41    // aligned, dereferenceable, and all of its bytes are initialized.
42    unsafe { Place::new_unchecked(place.pos() + i * mem::size_of::<T>(), ptr) }
43}
44
45impl<T: Archive, N: ArrayLength> Archive for GenericArray<T, N> {
46    const COPY_OPTIMIZATION: CopyOptimization<Self> =
47        unsafe { CopyOptimization::enable_if(T::COPY_OPTIMIZATION.is_enabled()) };
48
49    // NOTE: Because these are also GenericArray, methods should be
50    // #[inline(always)] to try to avoid extra stack allocations on
51    // input/output.
52    type Archived = GenericArray<T::Archived, N>;
53    type Resolver = GenericArray<T::Resolver, N>;
54
55    #[inline(always)]
56    fn resolve(&self, resolver: Self::Resolver, out: Place<Self::Archived>) {
57        for (i, (value, resolver)) in self.iter().zip(resolver).enumerate() {
58            let out_i = unsafe { index_for_place_generic_array(out, i) };
59            value.resolve(resolver, out_i);
60        }
61    }
62}
63
64impl<T, S, N: ArrayLength> Serialize<S> for GenericArray<T, N>
65where
66    T: Serialize<S>,
67    S: Fallible + ?Sized,
68{
69    #[inline(always)]
70    fn serialize(&self, serializer: &mut S) -> Result<Self::Resolver, S::Error> {
71        let mut result = core::mem::MaybeUninit::<Self::Resolver>::uninit();
72        let result_ptr = result.as_mut_ptr().cast::<T::Resolver>();
73        for (i, value) in self.iter().enumerate() {
74            unsafe {
75                result_ptr.add(i).write(value.serialize(serializer)?);
76            }
77        }
78        unsafe { Ok(result.assume_init()) }
79    }
80}
81
82impl<T, D, N: ArrayLength> Deserialize<GenericArray<T, N>, D> for GenericArray<T::Archived, N>
83where
84    T: Archive,
85    T::Archived: Deserialize<T, D>,
86    D: Fallible + ?Sized,
87{
88    #[inline(always)]
89    fn deserialize(&self, deserializer: &mut D) -> Result<GenericArray<T, N>, D::Error> {
90        let mut result = core::mem::MaybeUninit::<GenericArray<T, N>>::uninit();
91        let result_ptr = result.as_mut_ptr().cast::<T>();
92        for (i, value) in self.iter().enumerate() {
93            unsafe {
94                result_ptr.add(i).write(value.deserialize(deserializer)?);
95            }
96        }
97        unsafe { Ok(result.assume_init()) }
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use rkyv_0_8 as rkyv;
104
105    use crate::typenum::{U0, U32, U6};
106    use crate::{arr, GenericArray};
107    use rkyv::rancor::Error;
108    use rkyv::traits::{NoUndef, Portable};
109
110    const fn assert_portable_noundef<T: Portable + NoUndef>() {}
111    const _: () = assert_portable_noundef::<GenericArray<u8, U32>>();
112    const _: () = assert_portable_noundef::<GenericArray<u8, U0>>();
113
114    #[test]
115    fn test_rkyv_roundtrip() {
116        let array: GenericArray<u32, U6> = arr![1, 2, 3, 4, 5, 6];
117        let bytes = rkyv::to_bytes::<Error>(&array).unwrap();
118        let archived =
119            unsafe { rkyv::access_unchecked::<rkyv::Archived<GenericArray<u32, U6>>>(&bytes) };
120        for (i, el) in archived.iter().enumerate() {
121            assert_eq!(el.to_native(), array[i]);
122        }
123        let deserialized: GenericArray<u32, U6> =
124            rkyv::deserialize::<GenericArray<u32, U6>, Error>(archived).unwrap();
125        assert_eq!(deserialized, array);
126    }
127
128    // Exercises a `T` with a non-trivial `Resolver` and `Drop` (`String` archives via an
129    // out-of-line buffer, so `Resolver` carries position metadata that owns nothing but
130    // the deserialized `T` does). A regression in either Serialize or Deserialize that
131    // miscounted indices would surface here as a corrupted string or a leak under Miri.
132    #[cfg(feature = "alloc")]
133    #[test]
134    fn test_rkyv_roundtrip_string() {
135        use alloc::string::String;
136        use typenum::U3;
137
138        let array: GenericArray<String, U3> = arr![
139            String::from("hello"),
140            String::from("rkyv"),
141            String::from("world")
142        ];
143        let bytes = rkyv::to_bytes::<Error>(&array).unwrap();
144        let archived =
145            unsafe { rkyv::access_unchecked::<rkyv::Archived<GenericArray<String, U3>>>(&bytes) };
146        for (i, el) in archived.iter().enumerate() {
147            assert_eq!(el.as_str(), array[i].as_str());
148        }
149        let deserialized: GenericArray<String, U3> =
150            rkyv::deserialize::<GenericArray<String, U3>, Error>(archived).unwrap();
151        assert_eq!(deserialized, array);
152    }
153}
154
155#[cfg(all(test, feature = "bytecheck-0_8"))]
156mod tests_full {
157    use rkyv_0_8 as rkyv;
158
159    use crate::typenum::U6;
160    use crate::{arr, GenericArray};
161    use rkyv::rancor::Error;
162
163    #[test]
164    fn test_validated_roundtrip() {
165        let array: GenericArray<u32, U6> = arr![10, 20, 30, 40, 50, 60];
166        let bytes = rkyv::to_bytes::<Error>(&array).unwrap();
167        let deserialized: GenericArray<u32, U6> =
168            rkyv::from_bytes::<GenericArray<u32, U6>, Error>(&bytes).unwrap();
169        assert_eq!(deserialized, array);
170    }
171
172    #[test]
173    fn test_validation_rejects_truncated() {
174        let array: GenericArray<u32, U6> = arr![1, 2, 3, 4, 5, 6];
175        let bytes = rkyv::to_bytes::<Error>(&array).unwrap();
176        let truncated = &bytes[..bytes.len() - 1];
177        let result = rkyv::access::<rkyv::Archived<GenericArray<u32, U6>>, Error>(truncated);
178        assert!(result.is_err());
179    }
180}