Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I pass a function that creates a struct containing a Cell with a lifetime to another function?

Tags:

rust

lifetime

I'm trying to pass a constructor function as an argument to another function. The function creates a struct with an associated lifetime. I need to create a struct from this pointer after I have created some other objects that this struct can then reference. The example below seems to work:

struct Bar<'a> {
    number: Option<&'a usize>,
}

impl<'a> Bar<'a> {
    pub fn new() -> Bar<'a> {
        Bar { number: None }
    }
}

fn foo<'a, F>(func: &F)
where
    F: Fn() -> Bar<'a>,
{
    let number = 42;
    let mut bar = (func)();
    bar.number = Some(&number);
}

fn main() {
    foo(&Bar::new);
}

When I add a Cell for interior mutability then it does not compile:

use std::cell::Cell;

struct Bar<'a> {
    number: Cell<Option<&'a usize>>,
}

impl<'a> Bar<'a> {
    pub fn new() -> Bar<'a> {
        Bar {
            number: Cell::new(None),
        }
    }
}

fn foo<'a, F>(func: &F)
where
    F: Fn() -> Bar<'a>,
{
    let number = 42;
    let bar = (func)();
    bar.number.set(Some(&number));
}

fn main() {
    foo(&Bar::new);
}

Giving me the following error:

error[E0597]: `number` does not live long enough
  --> src/main.rs:21:26
   |
21 |     bar.number.set(Some(&number));
   |                          ^^^^^^ borrowed value does not live long enough
22 | }
   | - borrowed value only lives until here
   |
note: borrowed value must be valid for the lifetime 'a as defined on the function body at 15:1...
  --> src/main.rs:15:1
   |
15 | / fn foo<'a, F>(func: &F)
16 | | where
17 | |     F: Fn() -> Bar<'a>,
18 | | {
...  |
21 | |     bar.number.set(Some(&number));
22 | | }
   | |_^

Why did the first example work and not the second? Is there a way to specify a lifetime that exists for the scope let mut bar until the end of the function, rather than 'a which encompasses the entire function? Is this not possible without Non Lexical Lifetimes or Higher Kind Type Constructors etc?

like image 844
Drgabble Avatar asked Jun 28 '18 09:06

Drgabble


1 Answers

The compiler is smarter than you give it credit for. It has prevented you from introducing memory unsafety:

fn foo<'a, F>(func: &F)
where
    F: Fn() -> Bar<'a>,
{
    let number = 42;
    let bar = (func)();
    bar.number.set(Some(&number));
}

This code says that the caller of foo can specify a lifetime for 'a, but then the body of the method stores a reference into the value. That stored reference is not guaranteed to live that long. As an obvious example, the caller might require that 'a == 'static, but that would be impossible for the function to accomplish:

fn b() -> Bar<'static> {
    Bar {
        number: Cell::new(None),
    }
}

fn main() {
    foo(&b);
}

Note that this doesn't have anything to do with closures or functions:

use std::cell::Cell;

fn main() {
    let number = Cell::new(None);
    let x = 1;
    number.set(Some(&x));
    let y = 2;
    number.set(Some(&y));
}
error[E0597]: `x` does not live long enough
 --> src/main.rs:6:22
  |
6 |     number.set(Some(&x));
  |                      ^ borrowed value does not live long enough
...
9 | }
  | - `x` dropped here while still borrowed
  |
  = note: values in a scope are dropped in the opposite order they are created

Why did the first example work and not the second?

Because the compiler knows that Cell (really UnsafeCell) needs to account for the possibility that you will be storing a value in the created type.

From the Nomicon, emphasis mine:

  • UnsafeCell<T>, Cell<T>, RefCell<T>, Mutex<T> and all other interior mutability types are invariant over T (as is *mut T by metaphor)

Variance is a dense topic that I cannot explain succinctly.

@trentcl provides this example that shows that your original code may not be doing what you think it is.

Without the Cell, the compiler knows that it's safe to automatically adjust the lifetime of the returned type to one that's a little bit shorter. If we force the type to be the longer 'a, however, we get the same error:

fn foo<'a, F>(func: F)
where
    F: Fn() -> Bar<'a>,
{
    let number = 42;
    let mut bar: Bar<'a> = func();
    //           ^^^^^^^
    bar.number = Some(&number);
}
error[E0597]: `number` does not live long enough
  --> src/main.rs:17:24
   |
17 |     bar.number = Some(&number);
   |                        ^^^^^^ borrowed value does not live long enough
18 | }
   | - borrowed value only lives until here
   |
note: borrowed value must be valid for the lifetime 'a as defined on the function body at 11:1...
  --> src/main.rs:11:1
   |
11 | / fn foo<'a, F>(func: F)
12 | | where
13 | |     F: Fn() -> Bar<'a>,
14 | | {
...  |
17 | |     bar.number = Some(&number);
18 | | }
   | |_^

Is this not possible without [...]

Yes, but I'm not sure exactly what it would be. I believe it would need generic associated types (GAT) from RFC 1598.

My first thought was to try higher-ranked trait bounds (HRTB):

fn foo<F>(func: F)
where
    F: for<'a> Fn() -> Bar<'a>,
{
    let number = 42;
    let bar = func();
    bar.number.set(Some(&number));
}

This triggers E0582:

error[E0582]: binding for associated type `Output` references lifetime `'a`, which does not appear in the trait input types
  --> src/main.rs:17:25
   |
17 |     F: for <'a> Fn() -> Bar<'a>,
   |                         ^^^^^^^

To be honest, I cannot see the value in the code based on the example provided. If you are returning a Bar by value, you can make it mutable, removing any need for interior mutability.

You can also change the closure to take the value as needed:

fn foo<F>(func: F)
where
    F: for<'a> Fn(&'a i32) -> Bar<'a>,
{
    let number = 42;
    let bar = func(&number);
}

See also:

  • Is it possible to have a struct which contains a reference to a value which has a shorter lifetime than the struct?
  • How does for<> syntax differ from a regular lifetime bound?
like image 117
Shepmaster Avatar answered Nov 20 '22 06:11

Shepmaster