Trusted answers to developer questions
Trusted Answers to Developer Questions

Related Tags

scope
variables
memory
reference

What are generic lifetime parameters in a Rust function?

Educative Answers Team

Let’s define and call a compare function that takes two parameters of type i8 as references and returns the reference to the larger of the two:

fn main() {
    let x = 1;
    {
        let y = 2;
        let answer = compare(&x, &y);
        println!("The larger value is: {}", answer);
    }
}
fn compare(p: &i8, q: &i8) -> &i8 {
    if p > q {
        p
    } else {
        q
    }
}

Although the reference returned by compare (in the example above) will always be valid for the println! statement, the code won’t compile because there may be cases when only one of the returned references is valid. Consider the code below:

fn main() {
    let x = i8::new(1);
    let answer;
    {
        let y = i8::new(2);
        answer = compare(&x, &y);
    }
    println!("The larger value is: {}", answer);
}
fn compare(p: &i8, q: &i8) -> &i8 {
    if p > q {
        p
    } else {
        q
    }
}

If a reference to y is returned, it will be invalid when println! is executed as y will have gone out of scope. Rust uses lifetime parameters to avoid such potential run-time errors. Since the compiler doesn’t know in advance whether the if or the else block will execute, the code above won’t compile and an error message will be printed that says, “a lifetime parameter is expected in compare's signature.”

A generic lifetime parameter imposes a lifetime constraint on the reference(s) and the return value(s) of a function. While compiling the code, a generic lifetime is substituted for a concrete lifetime, which is equal to the smaller of the passed references’ lifetimes. This enables Rust to identify a violation of the constraint by a parameter or the variable storing the return value.

Annotation Syntax

The name of a lifetime parameter must start with an apostrophe ('), and be written after the function’s name in angle brackets as well as against the parameters and return type after the ampersand (&):

svg viewer
Name of generic lifetime: 'a

Now, let’s add the generic lifetimes to the compare function:

fn compare<'a>(p: &'a u8, q: &'a u8) -> &'a u8 {
    if *p > *q {
        p
    } else {
        q
    }
}
fn main() {
    let x = 1;                                       //----------+outer
    {                                                //          |
        let y = 2;                                   //----+inner|
        let answer = compare(&x, &y);                //    |     |
        println!("The larger value is: {}", answer); //    |     |
    }                                                //----+     |
}                                                    //----------+

Explanation

The two references passed to compare have different lifetimes (or scopes). Since y has a smaller lifetime, it becomes the concrete lifetime. This means that all the parameters that have 'a written next to them should have a lifetime at least as long as y. This also means that answer, which stores the returned reference, should be valid as long as y is valid.

Therefore, if we use answer out of the scope of y, ​we get a compilation error:

fn compare<'a>(p: &'a u8, q: &'a u8) -> &'a u8 {
    if *p > *q {
        p
    } else {
        q
    }
}
fn main() {
    let x = 1;                                   //----------+outer
    let answer;                                  //          |
    {                                            //          |
        let y = 2;                               //----+inner|
        answer = compare(&x, &y);                //    |     |
    }                                            //----+     |
    println!("The larger value is: {}", answer); //          |
}                                                //----------+

RELATED TAGS

scope
variables
memory
reference
Copyright ©2022 Educative, Inc. All rights reserved
RELATED COURSES

View all Courses

Keep Exploring