Introduction
Rust is a system programming language which guarantees memory safety and prevents problems associated with it like data races between threads, memory leaks and dangling pointers by taking radically new approach to memory management. In this article I'd like to discuss about one such approach which they call Lifetimes.
Brief intro to borrowing and ownership
Borrowing, ownership and lifetimes, all three of them are concepts in Rust which complement each other and provide memory guarantees at runtime. To keep our focus on Lifetimes we'll briefly see what others are.
Borrowing
Example: let x = &y
Here x
references y
and is "almost" similar in functionality to C/C++ references.
Ownership
Rust doesn't have a garbage collector, so it follows a simple rule to clear out allocated memory.
fn foo() {
let v = [1, 2, 3];
}
When v
goes out of scope it's memory will be cleared, even if it is dynamically allocated.
So what is Lifetime ?
In layman terms lifetime of a thing is "the length of time that thing is usable" and it means almost the same in Rust, the "thing" here being "variables".
Take a look at this code, and errors generated by compiler in the comments:
fn main() {
let r;
{
let i = 1;
r = &i; // borrow occurs here
} // `i` dropped here while still borrowed
println!("{}", r);
} // borrowed value needs to live until here
What happens here is:
- We declare a variable
r
- We go inside a scope
2.1. We initialize a variable
i
2.2. Assign a reference ofi
tor
- Go out of scope
- Use
r
At step 3, what happened was that memory for i
was freed and r
referred to a junk memory location.
The compiler gave us a sweet error message after step 4 saving us from a runtime disaster which C/C++ would have given us.
Okay, but why care ?
Let's see another example:
struct Car {
model: &str
}
You create a struct, and happily click that compile button, but...
error[E0106]: missing lifetime specifier
model: &str
^ expected lifetime parameter
you get this message. You're scratching your head and thinking, "I didn't do anything wrong".
Well, by compiler logic the reference model
will be destroyed as soon as the struct scope finishes. So how do you fix it:
struct Car<'a> {
model: &'a str
}
Notice the 'a
, we have explicitly assigned a name to variable's lifetime, then in the statement model: &'a str
we tell the compiler to keep the reference around for as long as we have the variable of type Car
.
Point to take note is that, lifetimes are always there but most of the times they are implied automatically by the compiler so you don't have to explicitly assign them each time you borrow a reference.
Leveling up
We'll look at another code:
struct Person<'a> {
name: &'a str,
do_task: &'a Fn(&str)
}
Here do_task
is a reference to a function and it borrows a string reference, "but... we didn't define &'a str
". &str
means it is tied to the scope of function, hence the string reference will be cleared when the function completes.
While do_task
and name
are tied to the scope of variable which is of Person
type.
Lifetimes is a new and interesting concept, but can be hard to understand the first time. Other resources you can look at are:
Happy coding!!
Top comments (1)
You made it really easy to understand. I started in Rust today and was wondering what is this &'a