Rust Lifetimes
Rust's lifetime mechanism is as crucial as the ownership mechanism for resource management.
The introduction of this concept primarily addresses the challenges of resource management in complex type systems.
References are indispensable when dealing with complex types, as complex data cannot be easily copied and processed by the processor.
However, references often lead to extremely complex resource management issues. First, let's understand dangling references:
Example
{
let r;
{
let x = 5;
r = &x;
}
println!("r: {}", r);
}
This code will not pass the Rust compiler because the value referenced by r has been released before it is used.
In the diagram, the green range 'a represents the lifetime of r, and the blue range 'b represents the lifetime of x. Clearly, 'b is much smaller than 'a, and references must be valid within the lifetime of the value.
We have been using String in structures instead of &str, and we will explain the reason with an example:
Example
fn longer(s1: &str, s2: &str) -> &str {
if s2.len() > s1.len() {
s2
} else {
s1
}
}
The longer function returns a reference to the longer of the two string slices, s1 and s2. However, this code will not compile because the return value reference might return an expired reference:
Example
fn main() {
let r;
{
let s1 = "rust";
let s2 = "ecmascript";
r = longer(s1, s2);
}
println!("{} is longer", r);
}
In this program, although the comparison is made, the source values s1 and s2 have expired when r is used. Of course, we can move the use of r within the lifetime of s1 and s2 to prevent this error, but for the function, it cannot know what is happening outside its scope. To ensure the values it passes out are valid, it must follow the ownership principle to eliminate all risks, so the longer function cannot compile.
Lifetime Annotations
Lifetime annotations are a way to describe the lifetimes of references.
Although this does not change the lifetime of the references, it allows declaring that two references have the same lifetime in appropriate places.
Lifetime annotations start with a single quote followed by a lowercase letter word:
&i32 // Regular reference
&'a i32 // Reference with lifetime annotation
&'a mut i32 // Mutable reference with lifetime annotation
Let's modify the longer function using lifetime annotations:
Example
fn longer<'a>(s1: &'a str, s2: &'a str) -> &'a str {
if s2.len() > s1.len() {
s2
} else {
s1
}
}
We need to use generic declarations to specify the lifetime names, and then the lifetime of the function's return value will be consistent with the lifetimes of the two parameters. Therefore, it can be called like this:
Example
fn main() {
let r;
{
let s1 = "rust";
let s2 = "ecmascript";
r = longer(s1, s2);
println!("{} is longer", r);
}
}
The combined result of the above two programs:
ecmascript is longer
Note: Don't forget the principle of automatic type inference.
Using String Slice References in Structs
This addresses the previous question:
Example
fn main() {
struct Str<'a> {
content: &'a str
}
let s = Str {
content: "string_slice"
};
println!("s.content = {}", s.content);
}
Execution result:
s.content = string_slice
If there are method definitions for the struct Str:
Example
impl<'a> Str<'a> {
fn get_content(&self) -> &str {
self.content
}
}
The return value here does not have a lifetime annotation, but adding one is not harmful. This is a historical issue; early versions of Rust did not support automatic lifetime inference, and all lifetimes had to be strictly declared, but the mainstream stable version of Rust now supports this feature.
Static Lifetime
(Note: The original text does not provide content for "Static Lifetime," so it is omitted in the translation.) The lifetime annotation has a special one: 'static. All string literals enclosed in double quotes have the exact data type of &'static str, where 'static represents a lifetime that spans from the start of the program to its end.
Generics, Traits, and Lifetimes Working Together
Example
use std::fmt::Display;
fn longest_with_an_announcement<'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
}
}
This program is from the Rust Bible and demonstrates the use of generics, traits, and lifetime mechanisms. It's not mandatory, but it's a good experience, as you'll likely encounter it sooner or later!