Skip to main content

rkyv/impls/core/with/
niching.rs

1use core::num::{NonZeroI8, NonZeroU8};
2
3use crate::{
4    boxed::ArchivedBox,
5    niche::{
6        niched_option::NichedOption,
7        niching::{
8            Bool, DefaultNiche, NaN, Niching, Null, SharedNiching, Zero,
9        },
10    },
11    primitive::{
12        ArchivedF32, ArchivedF64, ArchivedI128, ArchivedI16, ArchivedI32,
13        ArchivedI64, ArchivedNonZeroI128, ArchivedNonZeroI16,
14        ArchivedNonZeroI32, ArchivedNonZeroI64, ArchivedNonZeroU128,
15        ArchivedNonZeroU16, ArchivedNonZeroU32, ArchivedNonZeroU64,
16        ArchivedU128, ArchivedU16, ArchivedU32, ArchivedU64,
17    },
18    traits::ArchivePointee,
19    Place, Portable, RelPtr,
20};
21
22macro_rules! impl_default_niche {
23    ($ty:ty, $niche:ty) => {
24        impl Niching<$ty> for DefaultNiche {
25            unsafe fn is_niched(niched: *const $ty) -> bool {
26                unsafe { <$niche as Niching<$ty>>::is_niched(niched) }
27            }
28
29            fn resolve_niched(out: Place<$ty>) {
30                <$niche as Niching<$ty>>::resolve_niched(out)
31            }
32        }
33    };
34}
35
36// Zero
37
38macro_rules! impl_nonzero_zero_niching {
39    ($nz:ty, $int:ty) => {
40        impl Niching<$nz> for Zero {
41            unsafe fn is_niched(niched: *const $nz) -> bool {
42                let value = unsafe { &*niched.cast::<$int>() };
43                *value == 0
44            }
45
46            fn resolve_niched(out: Place<$nz>) {
47                let out = unsafe { out.cast_unchecked::<$int>() };
48                out.write(0.into());
49            }
50        }
51
52        impl_default_niche!($nz, Zero);
53    };
54}
55
56impl_nonzero_zero_niching!(NonZeroU8, u8);
57impl_nonzero_zero_niching!(ArchivedNonZeroU16, ArchivedU16);
58impl_nonzero_zero_niching!(ArchivedNonZeroU32, ArchivedU32);
59impl_nonzero_zero_niching!(ArchivedNonZeroU64, ArchivedU64);
60impl_nonzero_zero_niching!(ArchivedNonZeroU128, ArchivedU128);
61
62impl_nonzero_zero_niching!(NonZeroI8, i8);
63impl_nonzero_zero_niching!(ArchivedNonZeroI16, ArchivedI16);
64impl_nonzero_zero_niching!(ArchivedNonZeroI32, ArchivedI32);
65impl_nonzero_zero_niching!(ArchivedNonZeroI64, ArchivedI64);
66impl_nonzero_zero_niching!(ArchivedNonZeroI128, ArchivedI128);
67
68// NaN
69
70macro_rules! impl_float_nan_niching {
71    ($fl:ty, $ar:ty) => {
72        impl Niching<$ar> for NaN {
73            unsafe fn is_niched(niched: *const $ar) -> bool {
74                unsafe { (*niched).to_native().is_nan() }
75            }
76
77            fn resolve_niched(out: Place<$ar>) {
78                out.write(<$fl>::NAN.into());
79            }
80        }
81    };
82}
83
84impl_float_nan_niching!(f32, ArchivedF32);
85impl_float_nan_niching!(f64, ArchivedF64);
86
87// Bool
88
89impl Niching<bool> for Bool {
90    unsafe fn is_niched(niched: *const bool) -> bool {
91        unsafe { (*niched.cast::<u8>()) > 1 }
92    }
93
94    fn resolve_niched(out: Place<bool>) {
95        unsafe { out.cast_unchecked::<u8>().write(2) };
96    }
97}
98
99impl_default_niche!(bool, Bool);
100
101// Null
102
103impl<T> Niching<ArchivedBox<T>> for Null
104where
105    T: ArchivePointee + Portable + ?Sized,
106{
107    unsafe fn is_niched(niched: *const ArchivedBox<T>) -> bool {
108        unsafe { (*niched.cast::<RelPtr<T>>()).is_invalid() }
109    }
110
111    fn resolve_niched(out: Place<ArchivedBox<T>>) {
112        let out = unsafe { out.cast_unchecked::<RelPtr<T>>() };
113        RelPtr::emplace_invalid(out);
114    }
115}
116
117impl<T> Niching<ArchivedBox<T>> for DefaultNiche
118where
119    T: ArchivePointee + Portable + ?Sized,
120{
121    unsafe fn is_niched(niched: *const ArchivedBox<T>) -> bool {
122        unsafe { <Null as Niching<ArchivedBox<T>>>::is_niched(niched) }
123    }
124
125    fn resolve_niched(out: Place<ArchivedBox<T>>) {
126        <Null as Niching<ArchivedBox<T>>>::resolve_niched(out);
127    }
128}
129
130// SharedNiching
131
132impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
133where
134    T: SharedNiching<N1, N2>,
135    N1: Niching<T>,
136    N2: Niching<T>,
137{
138    unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool {
139        unsafe { <Self as Niching<T>>::is_niched(niched.cast()) }
140    }
141
142    fn resolve_niched(out: Place<NichedOption<T, N1>>) {
143        <Self as Niching<T>>::resolve_niched(unsafe { out.cast_unchecked() })
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use core::num::NonZeroU32;
150
151    use crate::{
152        api::test::{
153            deserialize, roundtrip_with, to_archived, to_archived_from_bytes,
154            to_bytes,
155        },
156        boxed::ArchivedBox,
157        niche::niching::{DefaultNiche, NaN, Zero},
158        with::{AsBox, MapNiche, NicheInto},
159        Archive, Deserialize, Serialize,
160    };
161
162    #[test]
163    fn with_struct() {
164        #[derive(Archive, Serialize, Deserialize, Debug, PartialEq)]
165        #[rkyv(crate, derive(Debug))]
166        struct Nichable {
167            #[rkyv(niche = NaN)]
168            not_nan: f32,
169            #[rkyv(niche = Zero)]
170            int: NonZeroU32,
171            #[rkyv(niche)] // Default = Bool
172            boolean: bool,
173        }
174
175        impl Nichable {
176            fn create() -> Self {
177                Nichable {
178                    not_nan: 123.456,
179                    int: unsafe { NonZeroU32::new_unchecked(789) },
180                    boolean: true,
181                }
182            }
183        }
184
185        #[derive(Archive, Serialize, Deserialize, Debug, PartialEq)]
186        #[rkyv(crate, derive(Debug))]
187        struct Middle {
188            #[rkyv(with = NicheInto<Zero>, niche = NaN, niche)]
189            // Default = Bool
190            a: Option<Nichable>,
191            #[rkyv(with = NicheInto<NaN>, niche = Zero)]
192            b: Option<Nichable>,
193        }
194
195        #[derive(Archive, Serialize, Deserialize, Debug, PartialEq)]
196        #[rkyv(crate, derive(Debug))]
197        struct Outer {
198            #[rkyv(with = DefaultNiche)]
199            field: Option<Middle>,
200        }
201
202        assert_eq!(
203            size_of::<ArchivedMiddle>(),
204            2 * size_of::<ArchivedNichable>()
205        );
206        assert_eq!(size_of::<ArchivedOuter>(), size_of::<ArchivedMiddle>());
207
208        let values = [
209            Outer { field: None },
210            Outer {
211                field: Some(Middle { a: None, b: None }),
212            },
213            Outer {
214                field: Some(Middle {
215                    a: None,
216                    b: Some(Nichable::create()),
217                }),
218            },
219        ];
220
221        roundtrip_with(&values[0], |_, archived| {
222            assert!(archived.field.is_none());
223        });
224        roundtrip_with(&values[1], |_, archived| {
225            let middle = archived.field.as_ref().unwrap();
226            assert!(middle.a.is_none());
227            assert!(middle.b.is_none());
228        });
229        roundtrip_with(&values[2], |_, archived| {
230            let middle = archived.field.as_ref().unwrap();
231            assert!(middle.a.is_none());
232            let b = middle.b.as_ref().unwrap();
233            assert_eq!(b.not_nan, 123.456);
234            assert_eq!(b.int.get(), 789);
235            assert_eq!(b.boolean, true);
236        });
237    }
238
239    #[test]
240    fn with_enum() {
241        #[derive(Archive, Serialize, Deserialize, Debug, PartialEq)]
242        #[rkyv(crate, derive(Debug))]
243        enum Nichable {
244            A(#[rkyv(niche)] bool),
245            B {
246                #[rkyv(niche = NaN)]
247                float: f32,
248            },
249            C,
250        }
251
252        #[derive(Archive, Serialize, Deserialize, Debug, PartialEq)]
253        #[rkyv(crate, derive(Debug))]
254        struct Middle {
255            #[rkyv(with = DefaultNiche, niche = NaN)]
256            nichable: Option<Nichable>,
257        }
258
259        #[derive(Archive, Serialize, Deserialize, Debug, PartialEq)]
260        #[rkyv(crate, derive(Debug))]
261        struct Outer {
262            #[rkyv(with = NicheInto<NaN>)]
263            field: Option<Middle>,
264        }
265
266        assert_eq!(size_of::<ArchivedNichable>(), size_of::<ArchivedMiddle>());
267        assert_eq!(size_of::<ArchivedOuter>(), size_of::<ArchivedMiddle>());
268
269        let values = [
270            Outer { field: None },
271            Outer {
272                field: Some(Middle { nichable: None }),
273            },
274            Outer {
275                field: Some(Middle {
276                    nichable: Some(Nichable::A(true)),
277                }),
278            },
279            Outer {
280                field: Some(Middle {
281                    nichable: Some(Nichable::B { float: f32::NAN }),
282                }),
283            },
284            Outer {
285                field: Some(Middle {
286                    nichable: Some(Nichable::B { float: 123.45 }),
287                }),
288            },
289            Outer {
290                field: Some(Middle {
291                    nichable: Some(Nichable::C),
292                }),
293            },
294        ];
295
296        roundtrip_with(&values[0], |_, archived| {
297            assert!(archived.field.is_none());
298        });
299        roundtrip_with(&values[1], |_, archived| {
300            let middle = archived.field.as_ref().unwrap();
301            assert!(middle.nichable.is_none());
302        });
303        roundtrip_with(&values[2], |_, archived| {
304            let middle = archived.field.as_ref().unwrap();
305            let nichable = middle.nichable.as_ref().unwrap();
306            match nichable {
307                ArchivedNichable::A(b) => assert!(*b),
308                _ => panic!("expected `ArchivedNichable::A`"),
309            }
310        });
311        to_archived(&values[3], |archived| {
312            // no roundtrip; NAN will be interpreted as being niched
313            assert!(archived.field.is_none());
314        });
315        roundtrip_with(&values[4], |_, archived| {
316            let middle = archived.field.as_ref().unwrap();
317            let nichable = middle.nichable.as_ref().unwrap();
318            match nichable {
319                ArchivedNichable::B { float } => {
320                    assert_eq!(float.to_native(), 123.45)
321                }
322                _ => panic!("expected `ArchivedNichable::B`"),
323            }
324        });
325        roundtrip_with(&values[5], |_, archived| {
326            let middle = archived.field.as_ref().unwrap();
327            let nichable = middle.nichable.as_ref().unwrap();
328            match nichable {
329                ArchivedNichable::C => {}
330                _ => panic!("expected `ArchivedNichable::C`"),
331            }
332        });
333    }
334
335    #[test]
336    fn map_niche() {
337        #[derive(Archive, Serialize, Deserialize, Debug, PartialEq)]
338        #[rkyv(crate, derive(Debug))]
339        struct Outer {
340            #[rkyv(with = MapNiche<AsBox>)]
341            opt: Option<NotNichable>,
342        }
343
344        #[derive(Archive, Serialize, Deserialize, Debug, PartialEq)]
345        #[rkyv(crate, derive(Debug))]
346        struct NotNichable {
347            int: i64,
348        }
349
350        let values = &[
351            Outer { opt: None },
352            Outer {
353                opt: Some(NotNichable { int: 42 }),
354            },
355        ];
356
357        to_bytes(&values[0], |bytes| {
358            assert_eq!(
359                bytes.len(),
360                size_of::<ArchivedBox<ArchivedNotNichable>>()
361            );
362            to_archived_from_bytes::<Outer>(bytes, |archived| {
363                assert!(archived.opt.as_ref().is_none());
364                let deserialized: Outer = deserialize(&*archived);
365                assert_eq!(&values[0], &deserialized);
366            });
367        });
368        roundtrip_with(&values[1], |_, archived| {
369            let bar = archived.opt.as_ref().unwrap();
370            assert_eq!(bar.int.to_native(), 42);
371        });
372    }
373}