12

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?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Drgabble
  • 618
  • 5
  • 17
  • 1
    This looks like a bug, related to some special handling of `Cell` by the compiler. See [this playground snippet](https://play.rust-lang.org/?gist=ea79b3576a84f620cdf0c4b5e3bf52a8&version=stable&mode=debug) where I've replaced the `std::cell::Cell` with a custom type having exactly the same constraints, but it doesn't exhibit the problem. – Peter Hall Jun 28 '18 at 12:07
  • Very interesting... so the lifetime definition as I've written it does match what I was attempting in your opinion? In which case if there aren't any any other answers I shall file a bug report. Exciting! @PeterHall would like to add your comment as an answer? – Drgabble Jun 28 '18 at 12:17
  • A thing to note — those aren't function pointers. A function pointer is of type `fn(...) -> ...`. You just have generic types that implement the `Fn*` traits (which don't have a succinct name AFAIK). You also don't need to take a reference to it, just pass it by value. – Shepmaster Jun 28 '18 at 13:36

1 Answers1

7

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:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • But in the first example, surely my reference to `number` also does not exists for the lifetime of `'a` (i.e. the entire function and possibly beyond)? Why does that one compile? – Drgabble Jun 28 '18 at 13:26
  • What exactly does invariant over T mean in this context? – Drgabble Jun 28 '18 at 13:29
  • Is there no way to say "infer the lifetime `'a` at the point where function `func` is called, not where the function `foo` is called"? – Drgabble Jun 28 '18 at 13:31
  • To address the final point in your question @Shepmaster, it is a slightly contrived example. For reasons outside the scope of this question the struct needs to be immutable - I was seeking to create a minimum example that demonstrated my issue with out cluttering it. I think I might open another question to better rephrase what I'm getting at, perhaps with a larger example. – Drgabble Jun 28 '18 at 13:42
  • @Drgabble yep, I figured it was an artifact of the example. – Shepmaster Jun 28 '18 at 13:44
  • Ooh, that final example might actually work with what I was looking for... I had originally tried it without the `&'a i32` and got the error `binding for associated type \`Output\` references lifetime \`'a\`, which does not appear in the trait input types`. But I could change it to take the input. – Drgabble Jun 28 '18 at 13:47
  • 1
    I'd like to see an explanation of the mechanism by which the compiler treats `Cell` differently from an apparently identical user-defined struct. The type of `Cell` does not communicate the invariance. – Peter Hall Jun 29 '18 at 06:29