Industrial Training




Rust Lifetime

    Lifetime defines the scope for which reference is valid.
    Lifetimes are implicit and inferred.
    Rust uses the generic lifetime parameters to ensure that actual references are used which are valid.

Preventing Dangling references with Lifetimes


When a program tries to access the invalid reference is known as a Dangling reference. The pointer which is pointing to the invalid resource is known as a Dangling pointer.


Let's see a simple example:
fn main()  
{  
  let a;  
  {  
    let b = 10;  
     a = &b;  
  }  
  println!("a : {}",a);  
}  

Output:

In the above example, the outer scope contains the variable whose named as 'a' and it does not contain any value. An inner scope contains the variable 'b' and it stores the value 10. The reference of 'b' variable is stored in the variable 'a'. When the inner scope ends, and we try to access the value of 'a'. The Rust compiler will throw a compilation error as 'a' variable is referring to the location of the variable which is gone out of the scope. Rust will determine that the code is invalid by using the borrow checker.


Borrow checker


The borrow checker is used to resolve the problem of dangling references. The borrow checker is used to compare the scopes to determine whether they are valid or not.



In the above example, we have annotated the lifetime of 'a' variable with the 'a and the lifetime of 'b' variable with the 'b. At the compile time, Rust will reject this program as the lifetime of 'a' variable is greater than the lifetime of 'b' variable. The above code can be fixed so that no compiler error occurs.



In the above example, the lifetime of 'a' variable is shorter than the lifetime of 'b' variable. Therefore, the above code runs without any compilation error.


Lifetime annotation syntax


    Lifetime annotation does not change how long any of the references live.
    Functions can also accept the references of any lifetime by using the generic lifetime parameter.
    Lifetime annotation describes the relationship among the lifetimes of multiple parameters.

Steps to be followed for the lifetime annotation syntax:
    The names of the lifetime parameters should start with (') apostrophe.
    They are mainly lowercase and short. For example: 'a.
    Lifetime parameter annotation is placed after the '&' of a reference and then space to separate the annotation from the reference type.

Some examples of lifetime annotation syntax are given below:
    &i32 // reference
    & 'a i32 // reference with a given lifetime.
    & 'a mut i32 // mutable reference with a given lifetime.

Lifetime Annotations in Function Signatures


The 'a represents the lifetime of a reference. Every reference has a lifetime associated with it. We can use the lifetime annotations in function signatures as well. The generic lifetime parameters are used between angular brackets <> , and the angular brackets are placed between the function name and the parameter list. Let's look:


fn fun<'a>(...);  

In the above case, fun is the function name which has one lifetime, i.e., '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'.

If 'y' is an immutable reference, then the parameter list would be:


fn fun< 'a>(y : & 'a i32);  

If 'y' is a mutable reference, then the parameter list would be:


fn fun< 'a>(y : & 'a mut i32);

Both & 'a i32 and & 'a mut i32 are similar. The only difference is that 'a is placed between the & and mut.
& mut i32 means "mutable reference to an i32" .
& 'a mut i32 means "mutable reference to an i32 with a lifetime 'a".


Lifetime Annotations in struct


We can also use the explicit lifetimes in the struct as we have used in functions.


Let's look:
struct Example  
  
   x : & 'a i32,  //  x is a variable of type i32 that has the lifetime 'a.  
        

Let's see a simple example:
   struct Example<'a> {  
    x: &'a i32,  
}  
fn main() {  
    let y = &9;   
    let b = Example{ x: y };  
    println!("{}", b.x);  
}  

Output:
9

impl blocks


We can implement the struct type having a lifetime 'a using impl block.


Let's see a simple 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 possibilities that we can have:


    Multiple references have the same lifetime.
    Multiple references have different lifetimes.

When references have the same lifetime.
fn fun < 'a>(x: & 'a i32 , y: & 'a i32) -> & 'a i32  
  
   //block of code.  

In the above case, both the references x and y have the same lifetime, i.e., 'a.


When references have the different lifetimes.
fn fun< 'a , 'b>(x: & 'a i32 , y: & 'b i32)   
  
   // block of code.  


In the above case, both the references x and y have different lifetimes, i.e., 'a and 'b respectively.


'static


The lifetime named as 'static is a special lifetime. It signifies that something has the lifetime 'static will have the lifetime over the entire program. Mainly 'static lifetime is used with the strings. The references which have the 'static lifetime are valid for the entire program.


Let's look:
let s : & 'static str = "javaTpoint tutorial" ;  

Lifetime Ellision


Lifetime Ellision is an inference algorithm which makes the common patterns more ergonomic. Lifetime Ellision makes a program to be ellided.


Lifetime Ellision can be used anywhere:
    & 'a T
    & 'a mut T
    T< 'a>

Lifetime Ellision can appear in two ways:

Input lifetime: An input lifetime is a lifetime associated with the parameter of a function.
Output lifetime: An output lifetime is a lifetime associated with the return type of the function.


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)
	{
	}

    If the single parameter is passed by reference, then the lifetime of that parameter is assigned to all the elided output lifetimes.

fn fun(x : i32, y : &i32) -> &i32
	{
	}
→
fn fun< 'a>(x : i32, y : & 'a i32) -> & 'a i3
	{
	}

    If multiple parameters passed by reference and one of them is &self or &mut self, then the lifetime of self is assigned to all the elided output lifetimes.

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.




Hi I am Pluto.