Rust Lifetimes: Preventing Dangling References
Lifetimes are Rust's compile-time guarantee that a reference never outlives the data it points to. The borrow checker uses them to prevent dangling pointers, a common source of bugs.
WHY IT EXISTS In systems languages like C++, it's easy to create a dangling reference—a pointer that refers to memory that has been deallocated. Accessing such a pointer leads to undefined behavior, crashes, and security vulnerabilities. Rust was designed to eliminate this entire class of bugs at compile time.
THE MENTAL MODEL Think of a lifetime as a named scope. A reference cannot exist in a scope that is "larger" or lasts "longer" than the scope of the data it points to. The compiler's borrow checker acts like a strict auditor, comparing the lifespan of the reference against the lifespan of the data. If the reference might outlive the data, the code simply won't compile.
HOW IT WORKS Every reference in Rust has a lifetime, which is the scope for which that reference is valid. Most of the time, the compiler infers these lifetimes automatically. However, when a function's signature involves multiple references (e.g., in its parameters or return value), the compiler may not be able to determine the safety of the references without help. In these cases, you use generic lifetime parameters, like 'a, to annotate the relationships. This tells the compiler, for example, "the returned reference is guaranteed to live at least as long as this specific input reference."
WHEN TO USE IT You will explicitly annotate lifetimes almost exclusively in function signatures that involve references. If a function takes two string slices and returns one, you need to tell the compiler which input's lifetime the output is tied to. The compiler is your guide here; it will produce a clear error message when an annotation is required.
WHEN NOT TO USE IT You don't need to annotate lifetimes for variables within a single function body if the relationships are unambiguous. The compiler's inference engine is powerful and handles the vast majority of local cases. Adding explicit lifetimes where they are not needed adds visual noise without providing any benefit.
ONE CANONICAL EXAMPLE Imagine a variable r declared in an outer scope. If an inner scope creates a variable x = 5, and we try to set r = &x, the code will fail to compile. When the inner scope ends, x is destroyed, but r still exists in the outer scope, now pointing to invalid memory. The borrow checker catches this because the lifetime of x is shorter than the lifetime of r. The fix is to ensure the data (x) lives at least as long as the reference (r), for instance by declaring both in the outer scope.
Read the original → doc.rust-lang.org
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.