Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lifetime error on struct referring to a parametrized function

Tags:

rust

If I write the code below, I get error[E0309]: the parameter type 'T' may not live long enough.

struct Function<T> {
    f: fn() -> T,
}

struct FunctionRef<'f, T> {
    f: &'f Function<T>,
}

This fixes the error:

struct FunctionRef<'f, T: 'f> {
    f: &'f Function<T>,
}

However, as far as I can tell, T is not bound on the lifetime 'f. Indeed, T is a new object created when the function of type fn () -> T is run.

Where am I missing something?

like image 334
Olivier Avatar asked Nov 20 '18 05:11

Olivier


1 Answers

Short answer: You need T: 'f because T may contains field that hold references and fn() -> T is covariant over T.

To simplify things may help to understand ...

For a moment substitute fn() -> T with T, because for me it is more simple to explain what it is happening with lifetimes. See the Note below why with this substitution does not change the lifetime related error.

struct Function<T> {
    f: T,
}

struct FunctionRef<'f, T> {
    f: &'f Function<T>,
}

This lead to the same error[E0309]: the parameter type 'T' may not live long enough error.

FunctionRef instances cannot outlive the reference it holds in f field: you declare a generic lifetime parameter 'f inside angle brackets and then you use 'f as an annotation inside the struct body. See also the book.

But FunctionRef::f depends on a type parameter T. The explicit constraint T: 'f guarantee that T instances does not outlive references holded by T and FunctionRef does not outlive FunctionRef::f.

If it can help to understand substitute the generic T with a specific Foo type:

struct Foo<'a> {
    n: &'a i32,
}

struct FunctionRef<'f, 'a: 'f> {
    f: &'f Foo<'a>,
}

You need to constraint lifetime 'a to be valid at least as long as 'f lifetime, otherwise memory safety rules would be violated.

Note

I have considered the case f: T equivalent of f: fn() -> T because such type constructor is covariant over T.

To grasp the meaning of fn() -> T is covariant over T, consider this struct:

struct Foo<'a> {
    v: &'a i32
}

In this case is safe to assign to v a value with a lifetime "bigger" than 'a, for example:

let ref_value: &'static i32 =  &100;
let foo = Foo { v: ref_value};

Now the same holds for the following struct:

struct Function<'a> {
    f: fn() -> &'a i32
}

The field f expects a function that returns a &i32 that outlive 'a.

In this case it is safe to pass a function that returns a &i32 with a "bigger" lifetime, for example:

fn my_f() -> &'static i32 {
    &100
}

fn main() {
    let foo = Function { f: my_f};
}

There is quite a lot of type theory behind this, see the nomicom for a detailed explanation.

like image 186
attdona Avatar answered Oct 26 '22 18:10

attdona