Skip to main content

commonware_codec/types/
lazy.rs

1//! This module exports the [`Lazy`] type.
2
3use crate::{Buf, BufsMut, Decode, Encode, EncodeSize, FixedSize, Read, Viewing, Write};
4use bytes::{Buf as _, Bytes};
5use core::hash::Hash;
6#[cfg(feature = "std")]
7use std::sync::OnceLock;
8
9/// A type which can be deserialized lazily.
10///
11/// This is useful when deserializing a value is expensive, and you don't want
12/// to immediately pay this cost. This type allows you to move this cost to a
13/// later point in your program, or use parallelism to spread the cost across
14/// computing cores.
15///
16/// # Usage
17///
18/// Any usage of the type requires that `T` implements [`Read`], because we need
19/// to know what type [`Read::Cfg`] is.
20///
21/// ## Construction
22///
23/// If you have a `T`, you can use [`Lazy::new`]:
24///
25/// ```
26/// # use commonware_codec::types::lazy::Lazy;
27/// let l = Lazy::new(4000u64);
28/// ```
29///
30/// or [`Into`]:
31///
32/// ```
33/// # use commonware_codec::types::lazy::Lazy;
34/// let l: Lazy<u64> = 4000u64.into();
35/// ```
36///
37/// If you *don't* have a `T`, then you can instead create a [`Lazy`] using
38/// bytes and a [`Read::Cfg`]:
39///
40/// ```
41/// # use commonware_codec::{Encode, types::lazy::Lazy};
42/// let l: Lazy<u64> = Lazy::deferred(&mut 4000u64.encode(), ());
43/// ```
44///
45/// ## Consumption
46///
47/// Given a [`Lazy`], use [`Lazy::get`] to access the value:
48///
49/// ```
50/// # use commonware_codec::{Encode, types::lazy::Lazy};
51/// let l = Lazy::<u64>::deferred(&mut 4000u64.encode(), ());
52/// assert_eq!(l.get(), Some(&4000u64));
53/// // Does not pay the cost of deserializing again
54/// assert_eq!(l.get(), Some(&4000u64));
55/// ```
56///
57/// This returns an [`Option`], because deserialization might fail.
58///
59/// ## Traits
60///
61/// [`Lazy`] can be serialized and deserialized, implementing [`Read`], [`Write`],
62/// and [`EncodeSize`], based on the underlying implementation of `T`.
63///
64/// Furthermore, we implement [`Eq`], [`Ord`], [`Hash`] based on the implementation
65/// of `T` as well. These methods will force deserialization of the value.
66#[derive(Clone)]
67pub struct Lazy<T: Read> {
68    /// This should only be `None` if `value` is initialized.
69    pending: Option<Pending<T>>,
70    #[cfg(feature = "std")]
71    value: OnceLock<Option<T>>,
72    #[cfg(not(feature = "std"))]
73    value: Option<T>,
74}
75
76#[derive(Clone)]
77struct Pending<T: Read> {
78    bytes: Bytes,
79    #[cfg_attr(not(feature = "std"), allow(dead_code))]
80    cfg: T::Cfg,
81}
82
83impl<T: Read> Lazy<T> {
84    // I considered calling this "now", but this was too close to "new".
85    /// Create a [`Lazy`] using a value.
86    #[cfg(feature = "std")]
87    pub fn new(value: T) -> Self {
88        Self {
89            pending: None,
90            value: OnceLock::from(Some(value)),
91        }
92    }
93
94    /// Create a [`Lazy`] using a value.
95    #[cfg(not(feature = "std"))]
96    pub const fn new(value: T) -> Self {
97        Self {
98            pending: None,
99            value: Some(value),
100        }
101    }
102
103    /// Create a [`Lazy`] by deferring decoding of an underlying value.
104    ///
105    /// Retains the remaining encoded bytes, sharing the input allocation when possible
106    /// and copying otherwise.
107    ///
108    /// Use [`Self::get`] to access the actual value, by decoding these bytes.
109    pub fn deferred(buf: &mut impl Buf, cfg: T::Cfg) -> Self {
110        let bytes = buf.copy_to_bytes(buf.remaining());
111        cfg_if::cfg_if! {
112            if #[cfg(feature = "std")] {
113                Self {
114                    pending: Some(Pending { bytes, cfg }),
115                    value: Default::default(),
116                }
117            } else {
118                Self {
119                    value: T::decode_cfg(Viewing::new(&bytes), &cfg).ok(),
120                    pending: Some(Pending { bytes, cfg }),
121                }
122            }
123        }
124    }
125
126    /// Force decoding of the underlying value.
127    ///
128    /// This will return `None` only if decoding the value fails.
129    ///
130    /// This function wil incur the cost of decoding the value only once,
131    /// so there's no need to cache its output.
132    #[cfg(feature = "std")]
133    pub fn get(&self) -> Option<&T> {
134        self.value
135            .get_or_init(|| {
136                let Pending { bytes, cfg } = self
137                    .pending
138                    .as_ref()
139                    .expect("Lazy should have pending if value is not initialized");
140                T::decode_cfg(Viewing::new(bytes), cfg).ok()
141            })
142            .as_ref()
143    }
144
145    /// Returns a reference to the underlying value, or `None` if decoding failed.
146    #[cfg(not(feature = "std"))]
147    pub const fn get(&self) -> Option<&T> {
148        self.value.as_ref()
149    }
150}
151
152impl<T: Read + Encode> From<T> for Lazy<T> {
153    fn from(value: T) -> Self {
154        Self::new(value)
155    }
156}
157
158// # Implementing Codec.
159//
160// The strategy here is that for writing, we use the underlying bytes stored
161// in the value, and for reading, we rely on the type having a fixed size.
162
163impl<T: Read + EncodeSize> EncodeSize for Lazy<T> {
164    fn encode_size(&self) -> usize {
165        if let Some(pending) = &self.pending {
166            return pending.bytes.len();
167        }
168        self.get()
169            .expect("Lazy should have a value if pending is None")
170            .encode_size()
171    }
172
173    fn encode_inline_size(&self) -> usize {
174        if self.pending.is_some() {
175            return 0;
176        }
177        self.get()
178            .expect("Lazy should have a value if pending is None")
179            .encode_inline_size()
180    }
181}
182
183impl<T: Read + Write> Write for Lazy<T> {
184    fn write(&self, buf: &mut impl bytes::BufMut) {
185        if let Some(pending) = &self.pending {
186            // Write raw bytes without length prefix (Bytes::write adds a length prefix)
187            buf.put_slice(&pending.bytes);
188            return;
189        }
190        self.get()
191            .expect("Lazy should have a value if pending is None")
192            .write(buf);
193    }
194
195    fn write_bufs(&self, buf: &mut impl BufsMut) {
196        if let Some(pending) = &self.pending {
197            // Write raw bytes without length prefix (Bytes::write_bufs adds a length prefix)
198            buf.push(pending.bytes.clone());
199            return;
200        }
201        self.get()
202            .expect("Lazy should have a value if pending is None")
203            .write_bufs(buf);
204    }
205}
206
207impl<T: Read + FixedSize> Read for Lazy<T> {
208    type Cfg = T::Cfg;
209
210    fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, crate::Error> {
211        // In this case, we can be a bit more helpful, and fail earlier, rather
212        // than deferring this error until later.
213        if buf.remaining() < T::SIZE {
214            return Err(crate::Error::EndOfBuffer);
215        }
216        Ok(Self::deferred(&mut buf.take(T::SIZE), cfg.clone()))
217    }
218}
219
220// # Forwarded Impls
221//
222// We want to provide some convenience functions which might exist on the underlying
223// value in a Lazy. To do so, we really on `get` to access that value.
224
225impl<T: Read + PartialEq> PartialEq for Lazy<T> {
226    fn eq(&self, other: &Self) -> bool {
227        self.get() == other.get()
228    }
229}
230
231impl<T: Read + Eq> Eq for Lazy<T> {}
232
233impl<T: Read + PartialOrd> PartialOrd for Lazy<T> {
234    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
235        self.get().partial_cmp(&other.get())
236    }
237}
238
239impl<T: Read + Ord> Ord for Lazy<T> {
240    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
241        self.get().cmp(&other.get())
242    }
243}
244
245impl<T: Read + Hash> Hash for Lazy<T> {
246    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
247        self.get().hash(state);
248    }
249}
250
251impl<T: Read + core::fmt::Debug> core::fmt::Debug for Lazy<T> {
252    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
253        self.get().fmt(f)
254    }
255}
256
257#[cfg(test)]
258mod test {
259    use super::Lazy;
260    use crate::{
261        Copying, Decode, DecodeExt, Encode, FixedSize, Read, Write, types::tests::TrackingWriteBuf,
262    };
263    use bytes::Bytes;
264    use proptest::prelude::*;
265
266    /// A byte that's always <= 100
267    #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
268    struct Small(u8);
269
270    impl FixedSize for Small {
271        const SIZE: usize = 1;
272    }
273
274    impl Write for Small {
275        fn write(&self, buf: &mut impl bytes::BufMut) {
276            self.0.write(buf);
277        }
278    }
279
280    impl Read for Small {
281        type Cfg = ();
282
283        fn read_cfg(buf: &mut impl crate::Buf, _cfg: &Self::Cfg) -> Result<Self, crate::Error> {
284            let byte = u8::read_cfg(buf, &())?;
285            if byte > 100 {
286                return Err(crate::Error::Invalid("Small", "value > 100"));
287            }
288            Ok(Self(byte))
289        }
290    }
291
292    impl Arbitrary for Small {
293        type Parameters = ();
294        type Strategy = BoxedStrategy<Self>;
295
296        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
297            (0..=100u8).prop_map(Small).boxed()
298        }
299    }
300
301    proptest! {
302        #[test]
303        fn test_lazy_new_eq_deferred(x: Small) {
304            let from_new = Lazy::new(x);
305            let from_deferred = Lazy::deferred(&mut x.encode(), ());
306            prop_assert_eq!(from_new, from_deferred);
307        }
308
309        #[test]
310        fn test_lazy_write_eq_direct(x: Small) {
311            let direct = x.encode();
312            let via_lazy = Lazy::new(x).encode();
313            prop_assert_eq!(direct, via_lazy);
314        }
315
316        #[test]
317        fn test_lazy_encode_consistent_across_construction(x: Small) {
318            let direct = x.encode();
319            let via_new = Lazy::new(x).encode();
320            let via_deferred = Lazy::<Small>::deferred(&mut x.encode(), ()).encode();
321            prop_assert_eq!(&direct, &via_new);
322            prop_assert_eq!(&direct, &via_deferred);
323        }
324
325        #[test]
326        fn test_lazy_read_eq_direct(byte: u8) {
327            let direct: Option<Small> = Small::decode(byte.encode()).ok();
328            let via_lazy: Option<Small> =
329                Lazy::<Small>::decode(byte.encode()).ok().and_then(|l| l.get().copied());
330            prop_assert_eq!(direct, via_lazy);
331        }
332
333        #[test]
334        fn test_lazy_cmp_eq_direct(a: Small, b: Small) {
335            let la = Lazy::new(a);
336            let lb = Lazy::new(b);
337            prop_assert_eq!(a == b, la == lb);
338            prop_assert_eq!(a < b, la < lb);
339            prop_assert_eq!(a >= b, la >= lb);
340        }
341    }
342
343    #[test]
344    fn test_lazy_view() {
345        let value: Vec<Lazy<Small>> = (0..64u8).map(|i| Lazy::new(Small(i))).collect();
346        let source = value.encode();
347        let cfg = ((..).into(), ());
348        let range = source.as_ptr_range();
349
350        // Decoding from the owned buffer defers every element as a view of it
351        let decoded = Vec::<Lazy<Small>>::decode_cfg(source.clone(), &cfg).unwrap();
352        assert_eq!(decoded, value);
353        let mut buf = TrackingWriteBuf::new();
354        decoded.write_bufs(&mut buf);
355        assert_eq!(buf.pushed.len(), value.len());
356        assert!(buf.pushed.iter().all(|b| range.contains(&b.as_ptr())));
357
358        // Decoding from a slice of it copies every element
359        let copied = Vec::<Lazy<Small>>::decode_cfg(Copying(&source), &cfg).unwrap();
360        assert_eq!(copied, value);
361        let mut buf = TrackingWriteBuf::new();
362        copied.write_bufs(&mut buf);
363        assert_eq!(buf.pushed.len(), value.len());
364        assert!(buf.pushed.iter().all(|b| !range.contains(&b.as_ptr())));
365    }
366
367    #[test]
368    fn test_lazy_get_shares_retained_bytes() {
369        let encoded = Bytes::from_static(b"hello").encode();
370        let range = encoded.as_ptr_range();
371        let lazy = Lazy::<Bytes>::deferred(&mut encoded.clone(), (..).into());
372        let value = lazy.get().unwrap();
373        assert_eq!(value, &b"hello"[..]);
374        assert!(range.contains(&value.as_ptr()));
375
376        // Retained fields after a scalar slice at their own offsets.
377        let fields = (
378            Bytes::from_static(b"hello"),
379            7u32,
380            Bytes::from_static(b"world"),
381        );
382        let fields = fields.encode();
383        let cfg = ((..).into(), (), (..).into());
384        let lazy = Lazy::<(Bytes, u32, Bytes)>::deferred(&mut fields.clone(), cfg);
385        let (first, scalar, second) = lazy.get().unwrap();
386        assert_eq!(first, &b"hello"[..]);
387        assert_eq!(*scalar, 7);
388        assert_eq!(second, &b"world"[..]);
389        assert_eq!(first.as_ptr(), fields[1..].as_ptr());
390        assert_eq!(second.as_ptr(), fields[11..].as_ptr());
391
392        // Trailing bytes fail decoding.
393        let mut extra = encoded.to_vec();
394        extra.push(0);
395        let lazy = Lazy::<Bytes>::deferred(&mut Bytes::from(extra), (..).into());
396        assert!(lazy.get().is_none());
397    }
398}