Skip to main content

dashu_int/third_party/
rkyv_v08.rs

1//! Implement rkyv 0.8 traits.
2//!
3//! Mirrors the [`super::rkyv_v07`] 0.7 module: `UBig`/`IBig` archive as their word representation
4//! (`ArchivedVec<Word::Archived>`, plus a sign flag for `IBig`), serialized allocation-free from
5//! the borrowed word slice. rkyv 0.8 archives multi-byte primitives **little-endian** by default,
6//! so the archived words are `u64_le`-style wrappers; reading them back needs a `to_native()`
7//! conversion per word. This module targets rkyv 0.8's `Place`-based `Archive` API and requires
8//! Rust ≥ 1.81 (rkyv 0.8's MSRV); it is stripped from the 1.68 MSRV build.
9
10use alloc::vec::Vec;
11use rkyv_v08 as rkyv;
12
13use crate::{IBig, UBig, Word};
14use dashu_base::Sign;
15
16/// The archived form of a `Word`: in rkyv 0.8 this is a little-endian wrapper (`u64_le`-style).
17type WordArchived = <Word as rkyv::Archive>::Archived;
18
19impl rkyv::Archive for UBig {
20    type Archived = rkyv::vec::ArchivedVec<WordArchived>;
21    type Resolver = rkyv::vec::VecResolver;
22
23    fn resolve(&self, resolver: Self::Resolver, out: rkyv::Place<Self::Archived>) {
24        rkyv::vec::ArchivedVec::<WordArchived>::resolve_from_slice(self.as_words(), resolver, out);
25    }
26}
27
28impl<S: rkyv::rancor::Fallible + rkyv::ser::Allocator + rkyv::ser::Writer + ?Sized>
29    rkyv::Serialize<S> for UBig
30{
31    #[inline]
32    fn serialize(&self, serializer: &mut S) -> Result<Self::Resolver, S::Error> {
33        rkyv::vec::ArchivedVec::<WordArchived>::serialize_from_slice(self.as_words(), serializer)
34    }
35}
36
37impl<D: rkyv::rancor::Fallible + ?Sized> rkyv::Deserialize<UBig, D>
38    for rkyv::vec::ArchivedVec<WordArchived>
39{
40    #[inline]
41    fn deserialize(&self, _: &mut D) -> Result<UBig, D::Error> {
42        let words: Vec<Word> = self.as_slice().iter().map(|w| w.to_native()).collect();
43        Ok(UBig::from_words(&words))
44    }
45}
46
47// `IBig` archives as `(is_negative, words)`; the two `ArchivedTuple2` fields are written with the
48// same `Place` projection rkyv's own tuple impl uses.
49impl rkyv::Archive for IBig {
50    type Archived = rkyv::tuple::ArchivedTuple2<bool, rkyv::vec::ArchivedVec<WordArchived>>;
51    type Resolver = (<bool as rkyv::Archive>::Resolver, rkyv::vec::VecResolver);
52
53    #[allow(clippy::unit_arg)] // `bool`'s resolver is `()`
54    fn resolve(&self, resolver: Self::Resolver, out: rkyv::Place<Self::Archived>) {
55        let (sign, words) = self.0.as_sign_slice();
56        unsafe {
57            // SAFETY: `out` points to a valid, aligned `ArchivedTuple2` being resolved
58            // field-by-field; the `addr_of_mut!` projections address the tuple's own fields, and
59            // `Place::from_field_unchecked` is the same projection rkyv's own tuple impl uses.
60            let out_ptr = out.ptr();
61            let ptr = core::ptr::addr_of_mut!((*out_ptr).0);
62            let out_field = rkyv::Place::from_field_unchecked(out, ptr);
63            <bool as rkyv::Archive>::resolve(
64                &matches!(sign, Sign::Negative),
65                resolver.0,
66                out_field,
67            );
68            let ptr = core::ptr::addr_of_mut!((*out_ptr).1);
69            let out_field = rkyv::Place::from_field_unchecked(out, ptr);
70            rkyv::vec::ArchivedVec::<WordArchived>::resolve_from_slice(
71                words, resolver.1, out_field,
72            );
73        }
74    }
75}
76
77impl<S: rkyv::rancor::Fallible + rkyv::ser::Allocator + rkyv::ser::Writer + ?Sized>
78    rkyv::Serialize<S> for IBig
79{
80    #[inline]
81    fn serialize(&self, serializer: &mut S) -> Result<Self::Resolver, S::Error> {
82        let (sign, words) = self.0.as_sign_slice();
83        <bool as rkyv::Serialize<S>>::serialize(&matches!(sign, Sign::Negative), serializer)?;
84        let words_resolver =
85            rkyv::vec::ArchivedVec::<WordArchived>::serialize_from_slice(words, serializer)?;
86        Ok(((), words_resolver))
87    }
88}
89
90impl<D: rkyv::rancor::Fallible + ?Sized> rkyv::Deserialize<IBig, D>
91    for rkyv::tuple::ArchivedTuple2<bool, rkyv::vec::ArchivedVec<WordArchived>>
92{
93    #[inline]
94    fn deserialize(&self, _: &mut D) -> Result<IBig, D::Error> {
95        let words: Vec<Word> = self.1.as_slice().iter().map(|w| w.to_native()).collect();
96        let mag = UBig::from_words(&words);
97        Ok(if self.0 {
98            -IBig::from(mag)
99        } else {
100            IBig::from(mag)
101        })
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn rkyv_ubig_roundtrip() {
111        let values = [
112            UBig::from(0u8),
113            UBig::from(1u8),
114            UBig::from(123456789u64),
115            UBig::from(1u8) << 100,
116            (UBig::from(3u8) << 300) + UBig::from(5u8),
117        ];
118        for v in &values {
119            let bytes = rkyv::to_bytes::<rkyv::rancor::Error>(v).unwrap();
120            // SAFETY: `bytes` came from `rkyv::to_bytes` on the same value, so it is a valid
121            // archived object with the root at the end.
122            let archived =
123                unsafe { rkyv::access_unchecked::<<UBig as rkyv::Archive>::Archived>(&bytes) };
124            let native: Vec<Word> = archived.as_slice().iter().map(|w| w.to_native()).collect();
125            assert_eq!(native, v.as_words(), "word view mismatch for {v}");
126            // SAFETY: `bytes` came from `rkyv::to_bytes` on the same value, so it is a valid
127            // archived object with the root at the end.
128            let v2 =
129                unsafe { rkyv::from_bytes_unchecked::<UBig, rkyv::rancor::Error>(&bytes) }.unwrap();
130            assert_eq!(v2, *v, "UBig round-trip failed for {v}");
131        }
132    }
133
134    #[test]
135    fn rkyv_ibig_roundtrip() {
136        let values = [
137            IBig::from(0),
138            IBig::from(-1),
139            IBig::from(1),
140            IBig::from(123456789i64),
141            IBig::from(-123456789i64),
142            (IBig::from(-1)) << 200,
143        ];
144        for v in &values {
145            let bytes = rkyv::to_bytes::<rkyv::rancor::Error>(v).unwrap();
146            // SAFETY: `bytes` came from `rkyv::to_bytes` on the same value, so it is a valid
147            // archived object with the root at the end.
148            let archived =
149                unsafe { rkyv::access_unchecked::<<IBig as rkyv::Archive>::Archived>(&bytes) };
150            assert_eq!(archived.0, v.sign() == Sign::Negative, "sign view mismatch for {v}");
151            let mag = v.clone().into_parts().1;
152            let native: Vec<Word> = archived
153                .1
154                .as_slice()
155                .iter()
156                .map(|w| w.to_native())
157                .collect();
158            assert_eq!(native, mag.as_words(), "word view mismatch for {v}");
159            // SAFETY: `bytes` came from `rkyv::to_bytes` on the same value, so it is a valid
160            // archived object with the root at the end.
161            let v2 =
162                unsafe { rkyv::from_bytes_unchecked::<IBig, rkyv::rancor::Error>(&bytes) }.unwrap();
163            assert_eq!(v2, *v, "IBig round-trip failed for {v}");
164        }
165    }
166}