Short-lived reference within struct with longer lifetime

Hi!

I want to have a "long-living" struct which has a Weak reference to another struct with a very short life time. Maybe some code will explain it better:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c0161064cb352c43d2b07f19152b4bb2

    use std::rc::{Rc, Weak};

// The long living thing
    #[derive(Debug)]
    struct A<'a> {
        b: B<'a>
    }

// The short living thing
    #[derive(Debug)]
    struct B<'a> {
        c: Weak<C<'a>>
    }

// Just some dummy object with a lifetime requirement
    #[derive(Debug)]
    struct C<'a> {
        s: &'a str
    }

    impl<'a> B<'a> {
        fn gen(&self ) -> C {
            C {
                s: "test"
            }
        }
    }

    impl<'a> A<'a> {
        fn run<'b>(&'b mut self, b: &'b B) {
// Here  'b is much short than 'a, but the Rc will be dropped at the end of the call - so the Weak ref should return None when upgraded outside of this function.
            let c = Rc::new(b.gen());
            self.b.c = Rc::downgrade(&c);
        }
    }

    fn main() {
        let mut a = A { b: B { c: Weak::new() } };
        a.run(&B { c: Weak::new() });
    }

I guess I could add another struct with the other structs as components and create it inside A::run().
But is there a way to keep A around and just updating the reference inside?

Here's the error I get for reference:

   Compiling playground v0.0.1 (/playground)
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
  --> src/main.rs:28:27
   |
28 |         let c = Rc::new(b.gen());
   |                           ^^^
   |
note: first, the lifetime cannot outlive the lifetime `'b` as defined on the method body at 27:12...
  --> src/main.rs:27:12
   |
27 |     fn run<'b>(&'b mut self, b: &'b B) {
   |            ^^
note: ...so that reference does not outlive borrowed content
  --> src/main.rs:28:25
   |
28 |         let c = Rc::new(b.gen());
   |                         ^
note: but, the lifetime must be valid for the lifetime `'a` as defined on the impl at 26:6...
  --> src/main.rs:26:6
   |
26 | impl<'a> A<'a> {
   |      ^^
note: ...so that the expression is assignable
  --> src/main.rs:29:20
   |
29 |         self.b.c = Rc::downgrade(&c);
   |                    ^^^^^^^^^^^^^^^^^
   = note: expected `std::rc::Weak<C<'a>>`
              found `std::rc::Weak<C<'_>>`

A struct must always live for a shorter time than all lifetimes annotated on it. So since the lifetime annotated on B is also used on the field containing C, whatever the lifetime of the string reference is, the B struct must live for a shorter time than that.

Generally, there are a lot of places in your example where lifetimes are elided, but I think it would make things a lot clearer if you typed out all the lifetimes.

If you make this change:

impl<'a> B<'a> {
    fn gen(&self) -> C<'a> { ... }
    //            new: ^^^^
}

And then follow the compiler error and add another annotation to fn run, it can resolve the lifetimes. Without the annotation above, the more explicit signature is

    fn gen<'b>(&'b self) -> C<'b> { ... } // and Self = B<'a>

due to the lifetime elision rules.

I can't tell if this is what you actually want or not from the code given however.

I understand. I was hoping that there was a way to "misuse" a Weak reference to circumvent the lifetime "issue" here.

Thanks! No, gen() was a placeholder, I was using it to force a longer life time.

I was trying to find a way to circumvent what @alice said: I wanted a struct which had a reference to something with a shorter life time. I thought Weak references might be way.
And I'm pretty sure they would be, weren't it for the lifetime parameter of the element in the Weak reference.

No. You can't circumvent lifetime rules in safe Rust, not even by introducing an arbitrary amount of indirection. Generally, what you want doesn't make much sense. What are you trying to use this type for?

It's fundamentally impossible, because lifetime annotations don't affect runtime semantics of the code in any way (they're literally removed before the code is compiled), so Weak has no way of knowing when the temporary borrow ended. It only knows when Rc is gone, but this is a completely separate mechanism from borrows' lifetimes.

Instead of having Rc that is scope-limited due to a temporary content, you should have used Rc in the innermost struct instead of a reference, e.g. s: Rc<str>.

1 Like

Let me explain what I meant - maybe it makes more sense then (pseudo-rust):

// Created once!
struct LongLived {
// Updated every few ms
  short_lived: ShortLived
}

struct ShortLived {...}

impl ShortLived {
  // Return &Other, if there is still a ShortLivedHolder pointing to it, None otherwise
   fn get() -> Option<&Other> {...}
}

// Created every few ms
struct ShortLivedHolder<'a> {
value: &'a Other
}

// Created *before* the above mentioned one
impl<X> ShortLivedHolder<X> {
  fn create_short_lived() -> ShortLived {...}
}

// Usage:
fn called_very_often<'a: 'b>(long_lived: &'a mut LongLived, other: &'b Other) {
  let short_lived_holder = ShortLivedHolder::new(other);
long_lived.short_lived = short_lived_holder.create_short_lived();
// ShortLived::get should return other, it is still valid within the scope of this function call

 a_lot_of_other_stuff(long_lived);
// ShortLivedHolder goes out of scope, and ShortLived::get will return None - the lifetime &b is definitely longer, since we dropped the reference here
}

The str was just a placeholder. In the "real" code, it is a repr(C) struct pointing to a shared memory location, which is only valid (safe) for the scope of the current function call.

I wanted to avoid resolving the id field (of the struct) over and over again. The "long lived" struct could just have the id in that case.

But I wanted to pass it on to further processing, therefore have the "real" struct reference for as long as it was valid.

I can do this:

struct LongLived { id:... }
struct ShortLived {id....}
struct ValidWithinFunctionCall<'a> { long_lived: &'a LongLived, short_lived: &'a ShortLived }

But I was just asking if it was possible to avoid the ValidWithinFunctionCall struct. Apparently it is not.

@alice, @kornel , @H2CO3 This is what I meant:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=bfadddd662bef38e5af25b5fbaa4fe96

Before I paste the code here. I'm pretty sure this is a bad idea, since I can't find any crates or std API doing this. It's also hard to do, because if the lifetime parameter for S.

But why would this be bad? Unless I get access to the Weak inside Leak - I cannot get a Rc<'a> with a long lifetime for 'a ie. 'static is not possible.

use std::rc::{Rc, Weak};

#[derive(Debug)]
struct S<'a>(&'a str);

struct Leak<T>(Weak<T>);

impl Leak<&'static S<'static>> {
    fn new<'a>(rc: &Rc<&'a S<'a>>) -> Self {
        let weak = Rc::downgrade(rc);
        // We expand to static lifetime, but we will never allow access to the static lifetime
        let weak = unsafe { std::mem::transmute::<Weak<&'a S<'a>>, Weak<&'static S<'static>>>(weak) };
        Self(weak)
    }
}

impl<'a> Leak<&'a S<'a>> {
    fn get<'b>(&'b self) -> Option<Rc<&'b S<'b>>> {
      // Here we shorten down the lifetime of the Rc
        self.0.upgrade()
    }
    
}

struct Old {
    leak: Leak<&'static S<'static>>
}

struct KillIt<'a> {
    s: &'a S<'a>
}

fn do_it(old: &mut Old, escape: &mut KillIt) {
    let s = S("Now you see me");
    let rc = Rc::new(&s);
    old.leak = Leak::new(&rc);
    println!("{:?}", old.leak.get());
// These would allow s from above te escape the local scope, but won't compile
//    escape.s = *old.leak.get().unwrap();
// Or are invalid anyways:
//    escape.s = *old.leak.get::<'static>().unwrap();
}

fn main() {
    let mut old = Old { leak: Leak(Weak::new()) };
    let mut kill_it = KillIt { s: &S("") };
    do_it(&mut old, &mut kill_it);
    println!("{:?}", old.leak.get());
}

You can upgrade the weak reference if you accidentally leak rc inside do_it:

std::mem::forget(rc); // add this at the end of do_it to invoke UB

Which demonstrates that Rc and Weak are really not buying you any safety here: what makes this sound is the exact runtime behavior of do_it. So with this particular code you are not invoking UB. But that's not generalizable to other things you could do inside do_it, probably including most interesting things you could do with old.leak.

2 Likes

Thanks!

Actually I was planning to do a lot in do_it. That would have been a real mess.
Your forget example was dead-on what I was looking for to break my code :joy:

1 Like