How to get static lifetime?


#1

I’m trying to capture a reference to a struct in a second struct that gets passed to a closure that has a 'static lifetime. Here’s the simplified example:

use std::rc::Rc;
use std::cell::{RefCell};

#[derive(Clone)]
struct S1 { data: u32 }
impl S1 {
    fn new(data: u32) -> Self {
        S1 { data: data }
    }
}

struct S2<'a> { s1: &'a RefCell<S1> }
impl<'a> S2<'a> {
    fn new(s1:&'a mut Rc<RefCell<S1>>) -> Self {
        S2 { s1: Rc::make_mut(s1) }
    }
}
    
fn closed<F: Fn(&Rc<RefCell<S2>>) + 'static >(f: &F) {
}

fn main() {
    let s1 = Rc::new(RefCell::new(S1 { data: 0}));
    let rs = s1.clone();
    let s2 = Rc::new(RefCell::new(S2::new(&mut rs)));
    closed(&move |_| {&s2;})
}

This fails with an issue with lifetimes:

test.rs:25:48: 25:50 error: rs does not live long enough
test.rs:25 let s2 = Rc::new(RefCell::new(S2::new(&mut rs)));
^~
note: reference must be valid for the static lifetime…
test.rs:24:25: 27:2 note: …but borrowed value is only valid for the block suffix following statement 1 at 24:24
test.rs:24 let rs = s1.clone();
test.rs:25 let s2 = Rc::new(RefCell::new(S2::new(&mut rs)));
test.rs:26 closed(&move |_| {&s2;})
test.rs:27 }
error: aborting due to previous error

IIUC, the data captured by the closure has static lifetime, because that’s the way closed (which isn’t mine to change in the real code) is declared. That’s s2, so the data it’s using - the reference to s1 - also needs a static lifetime. No problem, it should be around longer than s1 anyway. So how do I get an instance of s1 with a static lifetime?

Alternative solution: if I make S2 contain S1 instead of a reference to S1 to solve the lifetime issue, can I then get an immutable reference to s2.s1 in main? Not a copy, as that copy of s1 may be changed via s2, and I want to see the changed data.


#2

Since you are already wrapping your S1 type to get a Rc<RefCell<S1>> would it not be possible for S2 to be defined as struct S2 { s1: Rc<RefCell<S1>> } that way it has a 'static lifetime while still being able to access S1 in the same way as before.

Not sure if that solves your problem but the only actual way of getting a &'static T type is to put that T in a static variable but that does not seem like what you want.


#3

I’d be perfectly happy to have both structures have a static lifetime. That was what I was aiming at with the “alternative” solution: making S1 a part of S2 would give it static lifetime along with S2. But I couldn’t figure out how to get an immutable reference to just the S1 part of S2.

I’m just not sure how to declare a variable as static. The section on variables in the rust reference only talks about local variables. The section on lifetimes in the book talks about elision and using lifetimes in function/struct/impl blocks, but not about variables. Box seems like the data type I want, but the only way to get the pointer back is an unsafe call. I suspect I’ve missed something somewhere, but nothing I’ve tried parses, much less produces the right result.


#4

You can create statics with the static keyword; these values will have the lifetime 'static. However, these statics have to be defined at compile time where they are declared, and mutating them during program execution is an unsafe operation. I don’t think this is actually what you want to do: I think what you want to do is something fundamentally unsafe.

Something with the lifetime 'static means that none of the references in that object will ever become invalid. In order for that to be true, it either has to have no references, or only have references to items declared with the static keyword. You cannot ‘elevate’ a variable declared with let to be static, or anything like that.

In general, if a closure needs to have a 'static lifetime, that means it can’t capture references. A common example of this is the closure that is passed to thread::spawn: if it were allowed to capture references, the thread it was spawned from could die and those references would become invalid.


#5

My bad - I wasn’t clear here. I do know about const & static :slight_smile:. And that indeed solves the example, but it’s not usable in the real case, as elements of S1 are private and exposed in the code that’s creating it. That code needs to call ::new(), which of course it can’t do in a static declaration.

The question I should have asked was “how do I create a variable with 'static lifetiime at runtime”?


#6

A variable’s lifetime is its scope. Only statics (and in a hacky unsafe way, variables that you forget) have static scope.

Can you try to make the example more representative of your need? Currently the closure doesn’t try to access any data and ignores its argument. It’s also not clear why there are any muts there.


#7

Ok, here’s a version of main that creates a closure that’s more representative of what I’m actually doing:

fn main() {
    let s2 = Rc::new(RefCell::new(S2::new(0)));
    let mut rs = s2.clone();
    closed(&move |_| {rs.borrow_mut().s1.data = 23;})
}

You’ll need to #[derive(Clone)] for S2 as well.

For a longer explanation, this is an MVS application using rs-gtk. Closed connects the given closures to rs-gtk events that either query the user to get new values for data in both s1 & s2, or to use s1 methods to change it’s state and then trigger rs-gtk draw events that are going to use callbacks that query the state of s1 to update the display - which is where the need for an immutable reference to s1 comes from.


#8

In general, enough layers of Rc/RefCell will make anything work.

use std::rc::Rc;
use std::cell::RefCell;

#[derive(Clone)]
struct S1 { data: u32 }
impl S1 {
    fn new(data: u32) -> Self {
        S1 { data: data }
    }
}

struct S2 { s1: Rc<RefCell<S1>> }
impl S2 {
    fn new(s1: Rc<RefCell<S1>>) -> Self {
        S2 { s1: s1 }
    }
}
    
fn closed<F: Fn(&RefCell<S2>) + 'static>(f: &F) {}

fn main() {
    let s2 = Rc::new(S2::new(Rc::new(RefCell::new(S1 { data: 0 }))));
    let mut rs = s2.clone();
    closed(&move |_| { rs.s1.borrow_mut().data = 23;})
}

In specific cases it should be possible to simplify (or make less verbose) specific things. But describing code with words won’t get us far.


TWiR quote of the week
TWiR quote of the week
#9

Thanks. The trick seemed to be pushing the wrapper into the S2 struct. Just saving the RefCell didn’t work. My real application now compiles and runs without panicking on a borrow, so I can go back to thinking about the application!

I don’t think this can be simplified much: my two structs both need to be used in multiple closures (hence Rc wrappers), and mutable in some of them (hence RefCell wrappers). My attempt to simplify things by not storing only the RefCell wrapper in one of the structs seems to have backfired. if this isn’t right, I’d certainly appreciate knowing about it.


#10

One choice you’ll still have is where exactly to put the RefCell (and also note that for Copy types Cell is an alternative).

struct S1 { data: u32 }

impl S1 {
    fn work(&mut self) { ... }
}

fn main() {
    let s1 = Rc::new(RefCell::new(S1{ data: 0 }));
}

vs

struct S1 { data: Cell<u32> }

impl S1 {
    // interior mutability for real
    fn work(&self) { ... }
}

struct S2 {
    // now you don't need to wrap S1 in RefCell here and similarly
    // can avoid wrapping S2 in RefCell too
    s1: Rc<S1>,
}

fn main() {
    let s1 = Rc::new(S1{ data: 0 });
}