Skip to main content

vox_types/
selfref.rs

1#![allow(unsafe_code)]
2
3use std::mem::ManuallyDrop;
4use std::sync::Arc;
5
6/// A decoded value `T` that may borrow from its own backing storage.
7///
8/// Transports decode into storage they own (heap buffer, VarSlot, mmap).
9/// `SelfRef` keeps that storage alive so `T` can safely borrow from it.
10///
11/// Uses `ManuallyDrop` + custom `Drop` to guarantee drop order: value is
12/// dropped before backing, so borrowed references in `T` remain valid
13/// through `T`'s drop.
14// r[impl zerocopy.recv.selfref]
15pub struct SelfRef<T: 'static> {
16    /// The decoded value, potentially borrowing from `backing`.
17    value: ManuallyDrop<T>,
18
19    /// Backing storage keeping decoded bytes alive.
20    backing: ManuallyDrop<Backing>,
21}
22
23/// Backing storage for a [`SelfRef`].
24pub trait SharedBacking: Send + Sync + 'static {
25    /// Access backing bytes.
26    fn as_bytes(&self) -> &[u8];
27}
28
29// r[impl zerocopy.backing]
30pub enum Backing {
31    // r[impl zerocopy.backing.boxed]
32    /// Heap-allocated buffer (TCP read, BipBuffer copy-out for small messages).
33    Boxed(Box<[u8]>),
34    /// Shared backing that can be provided by transports (for example SHM slots).
35    Shared(Arc<dyn SharedBacking>),
36}
37
38impl Backing {
39    /// Wrap a transport-provided shared backing.
40    pub fn shared(shared: Arc<dyn SharedBacking>) -> Self {
41        Self::Shared(shared)
42    }
43
44    /// Access the backing bytes.
45    pub fn as_bytes(&self) -> &[u8] {
46        match self {
47            Backing::Boxed(b) => b,
48            Backing::Shared(s) => s.as_bytes(),
49        }
50    }
51}
52
53impl<T: 'static> Drop for SelfRef<T> {
54    fn drop(&mut self) {
55        // Drop value first (it may borrow from backing), then backing.
56        unsafe {
57            ManuallyDrop::drop(&mut self.value);
58            ManuallyDrop::drop(&mut self.backing);
59        }
60    }
61}
62
63impl<T: 'static> SelfRef<T> {
64    /// Construct a `SelfRef` from backing storage and a builder.
65    ///
66    /// The builder receives a `&'static [u8]` view of the backing bytes —
67    /// sound because the backing is heap-allocated (stable address) and
68    /// dropped after the value.
69    pub fn try_new<E>(
70        backing: Backing,
71        builder: impl FnOnce(&'static [u8]) -> Result<T, E>,
72    ) -> Result<Self, E> {
73        // Create a 'static slice from the backing bytes.
74        // Sound because:
75        // - Backing is heap-allocated (stable address)
76        // - We drop value before backing (custom Drop impl)
77        let bytes: &'static [u8] = unsafe {
78            let b = backing.as_bytes();
79            std::slice::from_raw_parts(b.as_ptr(), b.len())
80        };
81
82        let value = builder(bytes)?;
83
84        Ok(Self {
85            value: ManuallyDrop::new(value),
86            backing: ManuallyDrop::new(backing),
87        })
88    }
89
90    /// Infallible variant of [`try_new`](Self::try_new).
91    pub fn new(backing: Backing, builder: impl FnOnce(&'static [u8]) -> T) -> Self {
92        Self::try_new(backing, |bytes| {
93            Ok::<_, std::convert::Infallible>(builder(bytes))
94        })
95        .unwrap_or_else(|e: std::convert::Infallible| match e {})
96    }
97    /// Wrap an owned value that does NOT borrow from backing.
98    ///
99    /// No variance check — the value is fully owned. The backing is kept
100    /// alive but the value doesn't reference it. Useful for in-memory
101    /// transports (MemoryLink) where no deserialization occurs.
102    pub fn owning(backing: Backing, value: T) -> Self {
103        Self {
104            value: ManuallyDrop::new(value),
105            backing: ManuallyDrop::new(backing),
106        }
107    }
108
109    /// Transform the contained value, keeping the same backing storage.
110    ///
111    /// Useful for projecting through wrapper types:
112    /// `SelfRef<Frame<T>>` → `SelfRef<T>` by extracting the inner item.
113    ///
114    /// The closure receives the old value by move and returns the new value.
115    /// Any references the new value holds into the backing storage (inherited
116    /// from fields of `T`) remain valid — the backing is preserved.
117    /// Like [`try_map`](Self::try_map), but the closure also receives a `&'static [u8]`
118    /// view of the backing bytes, so the new value `U` can borrow from them.
119    pub fn try_repack<U: 'static, E>(
120        mut self,
121        f: impl FnOnce(T, &'static [u8]) -> Result<U, E>,
122    ) -> Result<SelfRef<U>, E> {
123        let value = unsafe { ManuallyDrop::take(&mut self.value) };
124        let backing = unsafe { ManuallyDrop::take(&mut self.backing) };
125        core::mem::forget(self);
126
127        let bytes: &'static [u8] = unsafe {
128            let b = backing.as_bytes();
129            std::slice::from_raw_parts(b.as_ptr(), b.len())
130        };
131
132        match f(value, bytes) {
133            Ok(u) => Ok(SelfRef {
134                value: ManuallyDrop::new(u),
135                backing: ManuallyDrop::new(backing),
136            }),
137            Err(e) => Err(e),
138        }
139    }
140
141    pub fn try_map<U: 'static, E>(
142        mut self,
143        f: impl FnOnce(T) -> Result<U, E>,
144    ) -> Result<SelfRef<U>, E> {
145        let value = unsafe { ManuallyDrop::take(&mut self.value) };
146        let backing = unsafe { ManuallyDrop::take(&mut self.backing) };
147        core::mem::forget(self);
148
149        match f(value) {
150            Ok(u) => Ok(SelfRef {
151                value: ManuallyDrop::new(u),
152                backing: ManuallyDrop::new(backing),
153            }),
154            Err(e) => Err(e),
155        }
156    }
157
158    pub fn map<U: 'static>(mut self, f: impl FnOnce(T) -> U) -> SelfRef<U> {
159        // SAFETY: we take both fields via ManuallyDrop::take, then forget
160        // self to prevent its Drop impl from double-dropping them.
161        let value = unsafe { ManuallyDrop::take(&mut self.value) };
162        let backing = unsafe { ManuallyDrop::take(&mut self.backing) };
163        core::mem::forget(self);
164
165        SelfRef {
166            value: ManuallyDrop::new(f(value)),
167            backing: ManuallyDrop::new(backing),
168        }
169    }
170}
171
172/// Trait for types that can be reborrowed with a shorter lifetime.
173///
174/// This is the key to `SelfRef` soundness: types stored inside `SelfRef`
175/// use a fake `'static` lifetime. `Reborrow` lets `get()` return a
176/// reference with the lifetime shortened to match the borrow, preventing
177/// the fake `'static` from leaking out.
178///
179/// Analogous to `yoke::Yokeable`.
180///
181/// # Safety
182///
183/// The implementing type must be **covariant** in its lifetime parameter,
184/// and `Self` and `Ref<'a>` must have identical memory layout for all `'a`.
185pub unsafe trait Reborrow: 'static {
186    /// The same type with a (possibly shorter) lifetime.
187    type Ref<'a>;
188}
189
190impl<T: Reborrow> SelfRef<T> {
191    /// Access the inner value with the lifetime properly shortened.
192    ///
193    /// Returns `&'a T::Ref<'a>` instead of `&'a T` — the inner lifetime
194    /// is tied to the borrow, so borrowed data cannot escape the `SelfRef`.
195    pub fn get(&self) -> &T::Ref<'_> {
196        // SAFETY: T is covariant in its lifetime (guaranteed by `unsafe trait
197        // Reborrow`), `T` and `T::Ref<'a>` have the same layout, and
198        // `'static: 'a` always holds.
199        unsafe { core::mem::transmute::<&T, &T::Ref<'_>>(&self.value) }
200    }
201}
202
203// No `into_inner()` — T may borrow from backing.
204// No `Deref` — would leak the fake `'static` lifetime. See `Reborrow` docs.
205// No `DerefMut` — mutating T could invalidate borrowed references.
206
207// SAFETY: these owned types have no lifetime parameter; Ref<'a> = Self is trivially sound.
208unsafe impl Reborrow for u32 {
209    type Ref<'a> = u32;
210}
211unsafe impl Reborrow for usize {
212    type Ref<'a> = usize;
213}
214
215unsafe impl Reborrow for i8 {
216    type Ref<'a> = i8;
217}
218
219unsafe impl Reborrow for i16 {
220    type Ref<'a> = i16;
221}
222
223unsafe impl Reborrow for i32 {
224    type Ref<'a> = i32;
225}
226
227unsafe impl Reborrow for i64 {
228    type Ref<'a> = i64;
229}
230
231unsafe impl Reborrow for i128 {
232    type Ref<'a> = i128;
233}
234
235unsafe impl Reborrow for u8 {
236    type Ref<'a> = u8;
237}
238
239unsafe impl Reborrow for u16 {
240    type Ref<'a> = u16;
241}
242
243unsafe impl Reborrow for u64 {
244    type Ref<'a> = u64;
245}
246
247unsafe impl Reborrow for u128 {
248    type Ref<'a> = u128;
249}
250
251unsafe impl Reborrow for f32 {
252    type Ref<'a> = f32;
253}
254
255unsafe impl Reborrow for f64 {
256    type Ref<'a> = f64;
257}
258
259unsafe impl Reborrow for bool {
260    type Ref<'a> = bool;
261}
262
263unsafe impl Reborrow for char {
264    type Ref<'a> = char;
265}
266
267unsafe impl Reborrow for String {
268    type Ref<'a> = String;
269}
270
271unsafe impl Reborrow for &'static str {
272    type Ref<'a> = &'a str;
273}
274
275/// Implement [`Reborrow`] for a list of types that are covariant in a single
276/// lifetime parameter.
277///
278/// # Safety
279///
280/// All listed types must be covariant in their lifetime parameter and
281/// `Foo<'static>` must have the same layout as `Foo<'a>` for all `'a`.
282#[macro_export]
283macro_rules! impl_reborrow {
284    ($($ty:ident),* $(,)?) => {
285        $(
286            // SAFETY: caller asserts covariance and layout compatibility.
287            unsafe impl $crate::Reborrow for $ty<'static> {
288                type Ref<'a> = $ty<'a>;
289            }
290        )*
291    };
292}
293
294/// Pattern-match on a field of a `SelfRef<T>`, projecting to `SelfRef<VariantInner>`
295/// in each arm body.
296///
297/// Uses `.get()` to peek at the discriminant without consuming, then `.map()` to
298/// project into the taken arm. The `unreachable!()` in each map closure is genuinely
299/// unreachable because the `matches!` guard ensures only the correct variant reaches it.
300///
301/// Variants not listed are silently consumed and dropped.
302///
303/// # Example
304///
305/// ```ignore
306/// selfref_match!(msg, payload {
307///     MessagePayload::RequestMessage(r) => { /* r: SelfRef<RequestMessage> */ }
308///     MessagePayload::ChannelMessage(c) => { /* c: SelfRef<ChannelMessage> */ }
309/// })
310/// ```
311#[macro_export]
312macro_rules! selfref_match {
313    (
314        $selfref:expr, $field:ident {
315            $( $first:ident $(:: $rest:ident)* ($binding:tt) => $body:block )*
316        }
317    ) => {{
318        let __sref = $selfref;
319        $(
320            if ::core::matches!(&__sref.get().$field, $first$(::$rest)*(_)) {
321                #[allow(unused_variables)]
322                let $binding = __sref.map(|__v| match __v.$field {
323                    $first$(::$rest)*(__inner) => __inner,
324                    _ => unreachable!(),
325                });
326                $body
327            } else
328        )*
329        {
330            // Unlisted variant — consume and drop.
331            let _ = __sref;
332        }
333    }};
334}
335
336#[cfg(test)]
337mod tests {
338    use super::*;
339    use std::sync::atomic::{AtomicBool, Ordering};
340
341    // SAFETY: test-only types with no lifetime parameter.
342    unsafe impl Reborrow for &'static [u8] {
343        type Ref<'a> = &'a [u8];
344    }
345    unsafe impl Reborrow for (u32, u8, u8) {
346        type Ref<'a> = (u32, u8, u8);
347    }
348
349    struct TestSharedBacking {
350        bytes: Vec<u8>,
351        dropped: Arc<AtomicBool>,
352    }
353
354    impl SharedBacking for TestSharedBacking {
355        fn as_bytes(&self) -> &[u8] {
356            &self.bytes
357        }
358    }
359
360    impl Drop for TestSharedBacking {
361        fn drop(&mut self) {
362            self.dropped.store(true, Ordering::Release);
363        }
364    }
365
366    struct DropOrderValue {
367        backing_dropped: Arc<AtomicBool>,
368        value_dropped_before_backing: Arc<AtomicBool>,
369    }
370
371    impl Drop for DropOrderValue {
372        fn drop(&mut self) {
373            let backing_is_dropped = self.backing_dropped.load(Ordering::Acquire);
374            self.value_dropped_before_backing
375                .store(!backing_is_dropped, Ordering::Release);
376        }
377    }
378
379    #[test]
380    fn try_new_builds_borrowing_value_from_backing() {
381        let backing = Backing::Boxed(Box::from([1_u8, 2, 3, 4]));
382        let sref = SelfRef::try_new(backing, |bytes| Ok::<_, ()>(&bytes[1..3]))
383            .expect("try_new should succeed");
384        assert_eq!(sref.get(), &[2_u8, 3]);
385    }
386
387    #[test]
388    fn try_new_propagates_builder_error() {
389        let backing = Backing::Boxed(Box::from([9_u8, 8, 7]));
390        let err = match SelfRef::<u32>::try_new(backing, |_| Err::<u32, _>("boom")) {
391            Ok(_) => panic!("try_new should return builder error"),
392            Err(err) => err,
393        };
394        assert_eq!(err, "boom");
395    }
396
397    #[test]
398    fn try_map_and_try_repack_preserve_backing_and_transform_value() {
399        let backing = Backing::Boxed(Box::from(*b"hello"));
400        let sref = SelfRef::new(backing, |bytes| bytes);
401        let len_ref = sref
402            .try_map(|bytes| Ok::<_, ()>(bytes.len()))
403            .expect("try_map should succeed");
404        assert_eq!(*len_ref.get(), 5);
405
406        let backing = Backing::Boxed(Box::from(*b"abcdef"));
407        let sref = SelfRef::new(backing, |_| 10_u32);
408        let repacked = sref
409            .try_repack(|value, bytes| Ok::<_, ()>((value + 1, bytes[0], bytes[5])))
410            .expect("try_repack should succeed");
411        assert_eq!(*repacked.get(), (11_u32, b'a', b'f'));
412    }
413
414    #[test]
415    fn try_map_and_try_repack_propagate_errors() {
416        let backing = Backing::Boxed(Box::from([1_u8, 2, 3]));
417        let sref = SelfRef::new(backing, |_| 7_u8);
418        let err = match sref.try_map::<u8, _>(|_| Err::<u8, _>("nope")) {
419            Ok(_) => panic!("try_map error should propagate"),
420            Err(err) => err,
421        };
422        assert_eq!(err, "nope");
423
424        let backing = Backing::Boxed(Box::from([4_u8, 5, 6]));
425        let sref = SelfRef::new(backing, |_| 9_u8);
426        let err = match sref.try_repack::<u8, _>(|_, _| Err::<u8, _>("bad")) {
427            Ok(_) => panic!("try_repack error should propagate"),
428            Err(err) => err,
429        };
430        assert_eq!(err, "bad");
431    }
432
433    #[test]
434    fn drop_order_drops_value_before_backing() {
435        let backing_dropped = Arc::new(AtomicBool::new(false));
436        let value_dropped_before_backing = Arc::new(AtomicBool::new(false));
437
438        let shared = Arc::new(TestSharedBacking {
439            bytes: vec![1, 2, 3],
440            dropped: Arc::clone(&backing_dropped),
441        });
442
443        let value = DropOrderValue {
444            backing_dropped: Arc::clone(&backing_dropped),
445            value_dropped_before_backing: Arc::clone(&value_dropped_before_backing),
446        };
447
448        let sref = SelfRef::owning(Backing::shared(shared), value);
449        drop(sref);
450
451        assert!(
452            value_dropped_before_backing.load(Ordering::Acquire),
453            "value should drop before backing"
454        );
455        assert!(
456            backing_dropped.load(Ordering::Acquire),
457            "backing should eventually be dropped"
458        );
459    }
460}