Lifetimes

Rust constantly wants to know "what exactly is that reference referencing?". Most things don’t live forever, so Rust also checks that developers don’t try to use it or reference it after it has been moved. A move is a change in ownership which potentially means physically moving it in memory and invalidating any pointers to it. drop(), for instance, takes ownership of an object so it can kill it. Anyone familiar with pointers in C has a decent understanding of the concept of pointer lifetimes: do not use the pointer after the object has been deleted or moved. As long as a shared reference exists, no mutable references may exist and the object must not be moved; and as long as a mutable reference exists, no other references may exist and the object must not be moved. The compiler enforces a more stringent test on safe code, that breaking those rules must provably never happen, leading to some cases where you know it will not happen, yet the compiler can not prove it, so it does not allow it. Luckily we do not need to follow the compiler’s test, we only need to follow those simple rules.

Unfortunately, for arbitrary code the lifetimes involved can get quite intricate. fn foo<’a>(input: &’a) -> TypeWithLifetime<’a> creates a transitive relationship between the lifetime of input and TypeWithLifetime<’a>. While we may be able to enforce a simple one-to-one lifetime relationship, it’s unclear if we can feasibly enforce that A lives as long as B lives as long as C lives as long as D lives as long as… Certainly, if it requires invasive changes to types crossing the FFI boundary, such as every reference in every struct needing to be converted to a RefCell<&T>, that would be very inconvenient for users.

Example of Lifetimes

The code does not explicitly use annotated lifetimes because it does not require them due to its simplicity. However, the concept is there implicitly:

struct PostfixCalculator {
    stack: VecDeque<f64>,
}

impl PostfixCalculator {
    fn new() -> Self {
        PostfixCalculator {
            stack: VecDeque::new(),
        }
    }
}

fn evaluate(&mut self, tokens: Vec<&str>) -> Result<f64, String>
{
    // use of `self` which has an implicit lifetime
}

This example implicitly uses lifetimes to ensure that references within the evaluate function do not outlive the PostfixCalculator instance they reference. Rust's lifetime elision rules automatically handle this in most cases, but explicit lifetime annotations can be used for more complex scenarios.

To learn more about lifetimes, it is recommended to read these official Rust resources: The Rust Programming Language chapter 10.3, and The Rustonomicon chapter 3.3.