Introduction
Lifetimes as the name says is how long references in Rust are active.
fn main() {
let r;
{
let x = 5;
r = &x;
}
println!("r: {}", r);
}
Error:
error[E0597]: `x` does not live long enough
--> src/main.rs:6:13
|
5 | let x = 5;
| - binding `x` declared here
6 | r = &x;
| ^^ borrowed value does not live long enough
7 | }
| - `x` dropped here while still borrowed
8 |
9 | println!("r: {}", r);
| - borrow later used here
For more information about this error, try `rustc --explain E0597`.
error: could not compile `basics` (bin "basics") due to previous error
x is declared inside the curly braces {} and the reference is dropped. Hence we get an error.
Borrow Checker
Let's now talk about the dreaded borrow checker of Rust, that is accused to make writing Programs in Rust very slow.
So in rust when we're passing any value by reference we're actually borrowing the value, and when the lifetime of the value is over, and we're still trying to use the borrowed value the compiler will throw an error, for Rusty reasons!
And this job of checking the scope of all variables and finding out if all the borrows being used are valid or not is done by the Borrow Checker, so it gets all the abuse, for the right reasons. Who will remember when we borrowed what, after writing JavaScript for more than 20 years!?
Anyway jokes apart, this is how the Rust Book shows the lifetimes of the variables for the above program.
fn main() {
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {}", r); // |
} // ---------+
'a is the lifetime for r which is valid for the whole duration of the program.
'b is the lifetime of x which is only valid inside the scope of the curly braces {}
So we're having an issue printing the value of r
fn main() {
let r;
{
let x = 5;
r = x.clone();
}
println!("r: {}", r);
}
This would work because we're cloing the value of x and storing in r which means now there are two memory locations storing 5, one for x, another for r
But when we're using reference, r is actually storing the reference of the value of x which is still being stored in its original memory location
And after the curly braces {} that memory is dropped and hence we have an error printing r
Simple only, but gives lot of pain!
Lifetime Annotations
Now that we understand lifetimes a lil bit, let's see how we can annotate lifetimes. Annotations don't change the scope of lifetimes, they only describe lifetimes. We write lifetime annotations with a '
&'a u64
&'b mut i32
&'a u64 means a variable of type u64 with lifetime 'a
&'b mut u64 means a variable of type i32 with lifetime 'b
This is how we can use lifetime annotations for a struct.
struct Blog<'a> {
title: &'a str,
author: &'a str,
body: String,
}
Here the struct Blog has a lifetime 'a
title & author also have the lifetime 'a
body on the otherhand is of the type String and owns its data.
Static Lifetimes
One common lifetime we'll use quite often is the 'static lifetime, which live for the entire duration of the Program execution. So in a way gives less headache. Now let's see how it works with an interesting example.
use tokio::net::TcpListener;
use axum::{
Router,
routing::get,
};
#[tokio::main]
async fn main() {
let app = Router::new().route("/", get(home_page_handler));
let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap();
println!("App running on Port {:#?}", listener.local_addr());
axum::serve(listener, app).await.unwrap();
}
async fn home_page_handler() -> &'static str {
"Blog App Home Page"
}
So the above code creates a simple web server using the awesome axum web framework in Rust. Now you don't have to worry about most of the things here for now, we'll cover them later. You only focus on the home_page_handler function. It returns a String slice with the 'static life time. Now if we browse "localhost:3000" in the browser we'll get "Blog App Home Page"
You can try this out for yourself!
Conclusion
That's all about Lifetimes for now. And I'll see you soon with another Rusty article. Cheers!