Reference captured in a struct with longer lifetime than the struct itself

Hello everyone,

I am struggling to understand, why following code does not compile, or if it is possible to achieve something similar at all

pub struct Wrap<'a, T> {
    wrap: &'a mut T,
}

fn retwrap<'b, 'a: 'b, T>(w: &'b Wrap<'a, T>) -> &'a T {
    w.wrap
}

The intention is to capture a reference in a struct, which outlives the struct itself and extract it later, the compiler complains that

    |
202 | fn retwrap<'b, 'a: 'b, T>(w: &'b Wrap<'a, T>) -> &'a T {
    |            --  -- lifetime `'a` defined here
    |            |
    |            lifetime `'b` defined here
203 |     w.wrap
    |     ^^^^^^ function was supposed to return data with lifetime `'a` but it is returning data with lifetime `'b`
    |
    = help: consider adding the following bound: `'b: 'a`

Many thanks !

It’s not possible, as it would be unsound. After the shorter lifetime 'b is over, Wrap<'a, T> allows mutable access to the T again. With retwrap’s signature, you can create an aliased mutable reference as in:

pub struct Wrap<'a, T> {
    wrap: &'a mut T,
}

fn retwrap<'b, 'a: 'b, T>(w: &'b Wrap<'a, T>) -> &'a T {
    todo!()
}

// this compiles!!!
fn alias<'a, T>(x: &'a mut T) -> (&'a mut T, &'a T) {
    let wrap = Wrap { wrap: x };
    let immut_ref = retwrap(&wrap);
    let mut_ref = wrap.wrap;
    (mut_ref, immut_ref)
}

Of course, this limitation is specific to mutable references. If Wrap contained an immutable reference as a field, wrap: &'a T, it would be trivial to extract an &'a T (immutable references can simply be copied).

8 Likes

Also, quinedot's awesome tutorial describes this scenario: Nested borrows and invariance - Learning Rust (quinedot.github.io)

  • You cannot get a &'long U or any &mut U from a &'short &'long mut U
    • You can only reborrow a &'short U
2 Likes

Additional note: of course it's possible to have reference that outlives your struct:

fn retwrap<'a, T>(w: Wrap<'a, T>) -> &'a T {
    w.wrap
}

Structure is destroyed in that function and reference returned.

But it's impossible to have short-living type which includes long-living reference.

The type here is full &'b Wrap<'a, T> and 'a must be shorter that 'b for it to be acceptable. Precisely because after that type becomes invalid both your Wrap<'a, T> and reference returned from that functions are accessible… and that couldn't be allowed, the whole point of unique mutable reference it that it's, well, unique. Mutability is an additional bonus. There was even attempt to rename &mut into &uniq but that wasn't accepted.

IOW: Rust types are designed specifically to prevent that thing that you are trying to do.

Define “similar”, please. Two independent unique references to the same object? No, that's the whole point.

Thanks for all the feedback, things are getting more clear.
I tried to minimize the use case, but the original code is more complicated and tries to implement a concept of a builder based on the typestate pattern for ffi C structs with MaybeUninit. There the Builder::as_ref (simplified code below) cannot use &self (or &mut self) to return basically the originally captured reference multiple times and I did not understand why. In the following code , Struct are replacements for C structs from bindgen, the Builder structs/impls are supposed to be generated. Another question would be, if it is possible to verify at compile time, that the reference passed to the closure in set_fn is the same as the returned one (instead of the assertion)

use std::{marker::PhantomData, mem::MaybeUninit};

#[derive(Debug)]
struct Struct<T> {
    field: T,
}

type A = Struct<u32>;
type B = Struct<A>;

pub struct Init;
pub struct UnInit;
struct Builder<'a, T, S> {
    tobuild: &'a mut MaybeUninit<T>,
    s: PhantomData<S>,
}

impl<'a, T> Builder<'a, Struct<T>, UnInit> {
    pub fn new(b: &'a mut MaybeUninit<Struct<T>>) -> Self {
        Self {
            tobuild: b,
            s: PhantomData,
        }
    }
    pub fn set_fn<F>(self, bfn: F) -> Builder<'a, Struct<T>, Init>
    where
        F: FnOnce(&mut MaybeUninit<T>) -> &T,
    {
        unsafe {
            let field = &mut self.tobuild.assume_init_mut().field;
            // can this be checked at compile time with lifetimes ?
            assert_eq!(
                field as *const _,
                bfn(std::mem::transmute(field)) as *const _
            );
        }
        Builder {
            tobuild: self.tobuild,
            s: PhantomData,
        }
    }
    pub fn set_val(self, val: T) -> Builder<'a, Struct<T>, Init>
    where
        T: Copy,
    {
        let s = unsafe { self.tobuild.assume_init_mut() };
        s.field = val;
        Builder {
            tobuild: self.tobuild,
            s: PhantomData,
        }
    }
}

impl<'a, T> Builder<'a, T, Init> {
    // this can't be &self
    fn as_ref(self) -> &'a T {
        unsafe { self.tobuild.assume_init_ref() }
    }
}

#[test]
fn test() {
    let mut b = MaybeUninit::<B>::uninit();
    let builder = Builder::new(&mut b);
    let builder = builder.set_fn(|b| Builder::new(b).set_val(1).as_ref());
    assert_eq!(dbg!(builder.as_ref()).field.field, 1);
    //this does not compile, builder is consumed above: dbg!(builder.as_ref());
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.