Rust Lifetime
Rust Lifetime
- The lifetime specifies the range of validity for a reference.
- Lifetimes are inferred and implicit.
- Rust makes advantage of the generic lifespan parameters to guarantee that legitimate references are used.
Preventing Dangling references with Lifetimes
A “dangling reference” is one that occurs when a software attempts to use an incorrect reference. A “dangling pointer” is a pointer that points to an incorrect resource.
Example:
fn main()
{
let a;
{
let b = 10;
a = &b;
}
println!("a : {}",a);
}
Output:
As demonstrated in the previous example, the variable ‘a’ in the outer scope is empty. 10. The variable ‘b’ in an inner scope holds the value 10. The variable ‘a’ has the reference to the ‘b’ variable. When the inner scope closes and we attempt to retrieve ‘a’s value. The variable ‘a’ refers to a location that is outside of its scope, so the Rust compiler will throw a compilation error. When Rust determines that the code is not valid, it will use the borrow checker.
Borrow checker
Dangling references are fixed with the help of the borrow checker. The scopes are compared using the borrow checker to see if they are valid or not.
The ‘a’ and ‘b’ annotations refer to the lifetimes of the ‘a’ and ‘b’ variables, respectively, in the example above. Since ‘a’ variable’s lifetime is longer than ‘b’ variable’s, Rust will reject this program at build time. It is possible to fix the code shown above to prevent compiler errors.
The ‘a’ variable in the example above has a shorter lifetime than the ‘b’ variable. As a result, there are no compilation errors when using the code above.
Lifetime annotation syntax
- The lifetime annotation has no effect on the lifespan of any references.
- The generic lifetime argument allows functions to accept references of any lifetime as well.
- The link between the lifetimes of several parameters is described by the lifetime annotation.
Steps to be followed for the lifetime annotation syntax:
- The lifespan parameter names ought to begin with the apostrophe (‘).
- They are mostly short and lowercase. For instance: ‘a.
- To distinguish the lifetime parameter annotation from the reference type, it is positioned after the ‘&’ of a reference and followed by a space.
Some examples of lifetime annotation syntax are given below:
- &i32 // reference
- & ‘a i32 // reference with a given lifespan.
- & ‘a mutable reference with a specified lifetime, i32.
Lifetime Annotations in Function Signatures
“a” stands for a reference’s lifetime. A lifetime is attached to each reference. Moreover, function signatures can make use of the lifespan annotations. Between the angle brackets <> and the function name, the generic lifespan parameters are used. The parameter list is placed between the angular brackets. Looking now:
fn fun<'a>(...);
Fun is the function name in the example above that has a single lifetime, that is,
‘a. If a function contains two reference parameters with two different lifetimes, then it can be represented as:
fn fun<'a,'b>(...);
If a function contains a single variable named as ‘y’.
Should ‘y’ be an unchangeable reference, the argument list would be as follows:
fn fun<'a>(y : & 'a i32);
If ‘y’ is a changeable reference, the following would be the parameter list:
fn fun<'a>(y : & 'a mut i32);
& ‘a mut i32 and & ‘a i32 are comparable. The placement of ‘a between the & and mut is the only distinction.
“Mutable reference to an i32” is what “& mut i32” denotes.
“Mutable reference to an i32 with a lifetime ‘a” is what “& ‘a mut i32” actually means.
Lifetime Annotations in struct
As with functions, we can also make advantage of the struct’s explicit lifetimes.
Let’s look:
struct Example
x : & 'a i32, // x is a variable of type i32 that has the lifetime 'a.
Example:
struct Example<'a> {
x: &'a i32,
}
fn main() {
let y = &9;
let b = Example{ x: y };
println!("{}", b.x);
}
Output:
9
impl blocks
Using an impl block, we can implement the struct type with lifetime ‘a.
Example:
struct Example<'a> {
x: &'a i32,
}
impl<'a> Example<'a>
{
fn display(&self)
{
print!("Value of x is : {}",self.x);
}
}
fn main() {
let y = &90;
let b = Example{ x: y };
b.display();
}
Output:
Value of x is : 90
Multiple Lifetimes
There are two options available to us:
- The lifetime of many references is the same.
- Different references have varying lifetimes.
When references have the same lifetime.
fn fun <'a>(x: & 'a i32 , y: & 'a i32) -> & 'a i32
//block of code.
The references x and y in the example above have the same lifespan, or ‘a.
When references have the different lifetimes.
fn fun<'a , 'b>(x: & 'a i32 , y: & 'b i32)
// block of code.
The references x and y in the example above have distinct lifetimes, denoted by ‘a and ‘b, respectively.
‘static
The lifetime referred to as “static” is unique. It indicates that something has a “static” lifetime, meaning it will last for the duration of the program. The strings are primarily used with “static lifetime.” Throughout the program, references with a “static lifetime” are valid.
Let’s look:
let s : & 'static str = "javaTpoint tutorial" ;
Lifetime Ellision
The inference algorithm Lifetime Ellision improves the ergonomics of common patterns. A program can be made to be ellided with Lifetime Ellision.
Lifetime Ellision can be used anywhere:
- & ‘a T
- & ‘a mut T
- T<‘a>
Lifetime Ellision can appear in two ways:
- Input lifetime: A lifetime connected to a function’s parameter is called an input lifetime.
- Output lifetime: A lifetime connected to the function’s return type is called an output lifetime.
Let’s look:
fn fun<'a>( x : & 'a i32); // input lifetime
fn fun<'a>() -> & 'a i32; // output lifetime
fn fun<'a>(x : & 'a i32)-> & 'a i32; // Both input and output lifetime.
Rules of Lifetime Ellision:
Each parameter passed by the reference has got a distinct lifetime annotation.
fn fun( x : &i32, y : &i32)
{
}
→
fn fun<'a , 'b>( x :& 'a i32, y : & 'b i32)
{
}
The lifetime of the single parameter is allocated to each of the elided output lifetimes if it is passed by reference.
fn fun(x : i32, y : &i32) -> &i32
{
}
→
fn fun<'a>(x : i32, y : & 'a i32) -> & 'a i3
{
}
The lifetime of self is allocated to all of the elided output lives if there are multiple parameters provided by reference, and one of them is &self or &mut self.
fn fun(&self, x : &str)
{
}
→
fn fun<'a,'b>(& 'a self, x : & 'b str) -> & 'a str
{
}
For Example:
fn fun( x : &str); // Elided form.
fn fun<'a>(x : & 'a str) -> & 'a str; // Expanded form.