🚀
11. Lifetimes

Lifetimes

Dangeling References

  • A dangling reference is a pointer that references a location in memory that may have been given to someone else, or that may have been deallocated.
fn main() {
    let r;
    {
        let x = 5;
        r = &x;
    }
 
    println!("r: {}", r);
}
⛔

Error: x does not live long enoug

  • The variable x is dropped when it goes out of scope, but the reference r still points to the memory location of x.

Lifetime Annotations

  • Lifetime annotations are used to specify the relationship between the lifetimes of references.
fn main() {
    let r;                  // ------------------+-- 'a
    {                       //                   |
        let x = 5;          // -+-- 'b           |
        r = &x;             //  |                |
    }                       // -+                |
                            //                   |
    println!("r: {}", r);   // ------------------+
}
  • The lifetime of r is 'a, and the lifetime of x is 'b.
  • The lifetime of x is shorter than the lifetime of r, which causes the error.

Borrow Checker

  • The Rust compiler has a borrow checker that compares scopes to determine whether all borrows are valid.

  • The borrow checker uses lifetimes to determine the validity of references.

fn main() {
    let x = 5;                  // ----------+-- 'b
                                //           |
    let r = &x;                 // -+-- 'a   |
                                //  |        |
    println!("r: {}", r);       //  |        |
}                               // -+---------+
 
  • No error is thrown because the lifetime of x is longer than the lifetime of r.

Generic Lifetime Annotations

fn main() {
    let string1 = String::from("abcd");
    let string2 = String::from("xyz");
 
    let result = longest(string1.as_str(), string2.as_str());
 
    println!("The longest string is {}", result);
}
 
fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
⛔

Error: missing lifetime specifier

  • The error is thrown because the borrow checker cannot determine the lifetimes of the references x and y.

    1. Since, x and y have different lifetimes, the compiler cannot determine the lifetime of the return value.

    2. Cannot determine the exact lifetime of the return value.


Generic lifetime annotations are used to specify the relationship between the lifetimes of references.

  1. &i32 -> References
  2. &'a i32 -> References with lifetime annotations
  3. &'a mut i32 -> Mutable references with lifetime annotations
  • &'a str specifies that the return value will have the same lifetime as the references x and y.

  • conventionally, lowercase letters are used for lifetime annotations.

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
  • if x is the longest string, the return value will have the same lifetime as x, and if y is the longest string, the return value will have the same lifetime as y.
fn main() {
    let string1 = String::from("abcd");
    {
        let string2 = String::from("xyz");
 
        let result = longest(string1.as_str(), string2.as_str());
 
        println!("The longest string is {}", result);
    }
}
 
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
$ cargo run
 
The longest string is abcd
  • the borrow checker checks if the smallest lifetime is valid for all references.
fn main() {
    let string1 = String::from("abcd");
    let result;
 
    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
    }
 
    print!("The longest string is {}", result);
}
 
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
⛔

Error: borrowed value does not live long enough

  • since the lifetime of string2 is shorter than the lifetime of result, which is a dangling reference.
fn main() {
    let string1 = String::from("abcd");
    let result;
 
    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
    }
 
    print!("The longest string is {}", result);
}
 
fn longest<'a>(x: &'a str, y: &str) -> &'a str {
    x
}
  • since y is not being returned, if lifetime of y is not specified, no error is thrown.

Lifetime in Structs

struct ImportantExcerpt<'a> {
    part: &'a str,
}
 
fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
 
    let first_sentence = novel.split('.').next().expect("Could not find sentence.");
 
    let i = ImportantExcerpt {
        part: first_sentence,
    };
}
  • The lifetime annotation 'a is used to specify that the lifetime of part is the same as the lifetime of the reference first_sentence.

Lifetime Elision

â„šī¸
  1. Each Parameters that is a reference gets its own lifetime parameter.

  2. If there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters.

  3. If there are multiple input lifetime parameters, but one of them is &self or &mut self, the lifetime of self is assigned to all output lifetime parameters.

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
 
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[..i];
        }
    }
 
    &s[..]
}
  • No errors are thrown because the lifetime of the return value is the same as the lifetime of the reference s.

Lifetime Annotations in Methods

 
struct ImportantExcerpt<'a> {
    part: &'a str,
}
 
impl<'a> ImportantExcerpt<'a> {
    fn level(&self) -> i32 {
        3
    }
}
 
fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
 
    let first_sentence = novel.split('.').next().expect("Could not find sentence.");
 
    let i = ImportantExcerpt {
        part: first_sentence,
    };
 
    println!("ImportantExcerpt level: {}", i.level());
}
  • The lifetime annotation 'a is used to specify that the lifetime of part is the same as the lifetime of the reference first_sentence.

Static Lifetime

  • 'static is a special lifetime that lasts for the entire duration of the program.
fn main () {
    let s: &'static str = "I have a static lifetime.";
}

Putting it all Together

  • Generics, Traits, and Lifetimes can be combined to create complex functions and data structures.
use std::fmt::Display;
 
fn longest_with_an_annoucement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str
where
    T: Display,
{
    println!("Announcement! {}", ann);
 
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
 
fn main() {}

Š 2024 Driptanil Datta.All rights reserved

Made with Love â¤ī¸

Last updated on Mon Oct 20 2025