The Lifetime Problem of Generic Monomorphism

fn main() {
    fn foo<T>(x: T, _m: T) -> T {
        x
    }

    let x = 0;
    let mut n;
    {
        let m = 10;
        n = foo(&x, &m);
    }
    println!("{}", n);
}

The above code will prompt after compilation:

error[E0597]: `m` does not live long enough
  --> src/main.rs:10:21
   |
9  |         let m = 10;
   |             - binding `m` declared here
10 |         n = foo(&x, &m);
   |                     ^^ borrowed value does not live long enough
11 |     }
   |     - `m` dropped here while still borrowed
12 |     println!("{}", n);
   |                    - borrow later used here

Also, what is the lifetime used for the generic parameter of the foo function after monomorphism in the following code

fn main() {
    trait Animal<'c>: Send + Sized {
        fn execute<'e, 'q: 'e, E: 'q>(self, _query: E)
        where
            'c: 'e,
            E: Animal<'q>,
        {
        }
    }

    impl<'a> Animal<'a> for &'_ i32 {}

    fn foo<'e, 'c: 'e, E>(_executor: E)
    where
        E: 'e + Animal<'c>,
    {
    }

    let x = 0;
    foo(&x);
}

My question is how is the lifetime limited during generic monomorphism? Do you have any relevant documents? Thank you for your reply

fn main() {
    trait Animal<'c>: Send + Sized {
        fn execute<'e, 'q: 'e, E: 'q>(self, _query: E)
        where
            'c: 'e,
            E: Animal<'q>,
        {
        }// constrains: 'c: 'e, 'q: 'e, E: 'q + Animal<'q>
    }

    impl<'c, 'e> Animal<'c> for &'e i32 {}

    fn foo<'e, 'c: 'e, E>(_executor: E)
    where
        E: 'e + Animal<'c>,
    {
    } // constrains: 'c: 'e, E: 'e + Animal<'c>, 'c == 'e (since `E: 'q + Animal<'q>`)

    let x = 0;
    foo(&x); // foo::<'e, 'e, &'e i32>(&'e x)
}

Part 1

The signature

    fn foo<T>(x: T, _m: T) -> T {

says that both parameters have to have the same type, and it also returns that same type. The type includes any lifetimes (e.g. the lifetime of a reference). So here:

    {
        let m = 10;
        n = foo(&x, &m);
    }

The lifetime of &m can't be larger than the inner block, or it would dangle. The lifetime of &x and that in the value stored in n must be the same, because they're all the same type parameter in the call. Thus n cannot be usable outside the inner block.

(Additionally the function signature is the contract, and the contract allows you to return _m and not x from foo. The body of the function doesn't enter into lifetime checking at the call site, only the signature. The compiler has to assume you might return _m, since that's what the signature means.)

Part 2

The lifetime is any one that meets the constraints imposed by lifetime bounds and how the values are used. In this case you can consider the lifetime to be one that ends immediately after the call to foo as there is nothing causing it to last longer.

5 Likes

Thank you for your reply. I understand the meaning

Thank you for your reply. Let me think about it

fn main() {
    fn bar<'b, 'a: 'b>(x: &'a i32, _m: &'b i32) -> &'a i32 {
        x
    }

    let x = 1;
    let n;
    {
        let m = 10;
        n = bar(&x, &m);
    }
    println!("{}", n);
}

The above code compiles normally after adding lifetimes parameters.
I understand the usage of lifetimes, but I do not understand how to add lifetimes constraints to reference parameters during normalization of generic parameters.

Due to the different lifetimes, the parameters are now different types. In the generic version, try using two type parameters instead of one.

You need not require the returned types lifetime (or lifetime bound in the case of generics) to be longer for the given example.

2 Likes

You don't need 'a: 'b constraint, and write fn bar<'b, 'a> instead. The reason is you're returning a reference only related to 'a and irrelevant to 'b, so 'a and 'b are not connected.

The basic idea of lifetime annotation is to convey the intent of lifetime connections and contract the caller must adhere.

1 Like
fn main() {
    fn foo<T>(x: T, _m: T) -> T {
        x
    }

    let x = 0;
    let mut n;
    {
        let m = 10;
        n = foo(&x, &m);
    }
    println!("{}", n);
}

In the generic example, it is because during generic monomorphism, two parameters use the same lifetimes parameter, resulting in a failure of lifetimes checking. When a function has two or more generic parameters and the type of the parameter is a reference type, are all parameters using the same lifetimes during generic monomorphism?

Yes. quinedot already gave the answer: the same T means the same type (with same generics and trait bounds if needed).

It depends on the lifetime annotation.

2 Likes
    trait Animal<'c>: Send + Sized {
        fn execute<'e, 'q: 'e, E: 'q>(&self, _query: E)
        where
            'c: 'e,
            E: Animal<'q>,
        {
        }
    }

    impl<'c> Animal<'c> for &'_ i32 {
        fn execute<'e, 'q: 'e, E: 'q>(&self, _query: E)
        where
            'c: 'e,
            E: Animal<'q>,
        {
        }
    }

    fn foo<'e, 'c: 'e, E>(_executor: E)
    where
        E: 'e + Animal<'c>,
    {
    }

    let x = 0;
    foo(&x);

What is the lifecycle of the &self on the execute method parameter in the impl block? Thank you for your reply!

Just a normal shared reference with its own temporary lifetime.

    impl<'c> Animal<'c> for &'_ i32 {
        fn execute<'e, 'q: 'e, E: 'q>(&self, _query: E) // &self is &'tmp &'_ i32

Perhaps this signature would help with your problem? Here the reference is not part of the type parameter, so each function parameter can have a different lifetime while still sharing a common referent type.

fn bar<'b, 'a: 'b, T>(x: &'a T, _m: &'b T) -> &'a T {
    x
}
2 Likes

Thank you very much for your reply! I need to think about it

fn main() {
    fn foo<T>(x: T, _m: T) -> T {
        x
    }
    
    fn bar(x: &i32, _m: &i32) -> &i32 {
        x
    }
    
    let (x, y) = (1, 2);
    
    foo(&x, &y);
    
    bar(&x, &y);
}

What is the difference in lifetime between the foo and bar functions in the above code? Why can foo function compilation pass? Thank you for your reply!

The lifetimes are independent in bar but must be the same in foo.

Both compile as there's nothing conflicting with the lifetime being the same in this code.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.