DEV Community

Dsysd Dev
Dsysd Dev

Posted on

Understanding Lifetime in Rust

When learning Rust, the first time one encounters a compilation error that states about lifetimes, can be a confusing experience.

The first thing to understand about lifetimes is that it provides yet another level of security to our program.

As we all know and probably do, sharing references is a lot better than passing data by copying their values.

Though, references are easily out of scope when not considered carefully.

So for instance

let mut r: &Vec<u32>;
{
   let x = vec![1,2,3];
   r = &x;
}     // <-- the value owned by x is dropped
r    // <-- r points to a dropped value: ERROR!
Enter fullscreen mode Exit fullscreen mode

The “life” of x‘s value is bound to the code block wrapped by { ... }. Therefore, the “life” of a reference like r pointing to this value, exactly has the same “time of life”.

All good — and the Rust compiler would complain when detected an situation like the one above.

Another (C++ -traditional) example is the following:

fn f() -> &Vec<u32> {
  let x = vec![1,2,3];
  &x  // <-- life of x's owned value ends: ERROR!
}
Enter fullscreen mode Exit fullscreen mode

Also here, the compiler would complain since the returned references points to a dropped value.

Note that this wouldn't be a problem in case of golang, infact we do this commonly in go

func f() *[]uint32 {
  x := []uint32{1,2,3)
  return &x
}
Enter fullscreen mode Exit fullscreen mode

And we would let garbage collector take care of that.

So far, there was no need for any lifetime parameter because Rust was able to detect all the bad scenarios for us. But let us look at this one:

struct S{
  x_ref: &Vec<u32>   
}     // does not compile!

fn f() -> S{
  let x = vec![1,2,3];
  S{ x_ref: &x }
}  // <-- life of x's owned value ends
Enter fullscreen mode Exit fullscreen mode

In comparison with the previous example, here we kind of try to trick the compiler.

Instead of a reference, we now try to return a variable that owns its value. But this variable, i.e. the instance of S, wraps a reference to the value owned by x.

However, this reference after being returned again points to a dropped value.

Rust, will not complain at the same position as before, but instead does not allow us to declare a struct containing references without lifetime bounds. So it forces us to write:

struct S<'a>{
  x_ref: &'a Vec<u32>   
}     

fn f<'a>() -> S<'a> {
  let x = vec![1,2,3];
  S{ x_ref: &x }
}  // <-- life of referrence x_ref is shorter than life of S: ERROR!
Enter fullscreen mode Exit fullscreen mode

The compiler now detects a breach in the use of S, that is, it is tried to have an instance of S containing a reference which lives shorter than the instance itself.

Note, the instance lives longer since it is returned, whereas the by x_ref referred value is getting dropped at the end of the function.

As you can see, lifetime parameters is just a way the compilers ensures its checks do run properly.

Most of the time, the compiler is telling us automatically where these bounds are required.

Most important to note, lifetimes do not alter be any means the “life” of a value! Lifetimes are to be considered as additional information of a contract that we do have with the compiler.

For instance, you could have a function looking like

fn f<'a, 'b, T>(x: &'a T, y: &'b T, z: &'a T) -> &'a T {
    ...
}
Enter fullscreen mode Exit fullscreen mode

No matter what the function’s body is, this just tells us the following thing:

The return value of f cannot be used longer than x‘s and z’s life is.

An alternative interpretation is this: The use of f‘s return value, determines how long the passed references x and z must live at least.

The latter interpretation once more underlines that lifetime parameter are additional contract information the compiler from time to time requests from us.

Why is this all worth the hassle?

Rust’s concept of ownership and rules of borrowing is a lot new material to learn when coming from traditional languages.

Especially, with regard to the concept of lifetime parameter that introduces yet another layer of syntactical symbols, you might be tempted to ask: “Why not just keep the value until no reference points to it anymore?”

The answer, as easy it is, as easy can be forgotten: “By this, Rust does not need a garbage collector, nor you have to garbage collect on yourself.”

Thank you for reading!

Claps Please!

If you found this article helpful I would appreciate some claps 👏👏👏👏, it motivates me to write more such useful articles in the future.

Follow for regular awesome content and insights.

Subscribe to my Newsletter

If you like my content, then consider subscribing to my free newsletter, to get exclusive, educational, technical, interesting and career related content directly delivered to your inbox

https://dsysd.beehiiv.com/subscribe

Important Links

Thanks for reading the post, be sure to follow the links below for even more awesome content in the future.

Twitter: https://twitter.com/dsysd_dev
Youtube: https://www.youtube.com/@dsysd-dev
Github: https://github.com/dsysd-dev
Medium: https://medium.com/@dsysd-dev
Email: dsysd.mail@gmail.com
Telegram 📚: https://t.me/dsysd_dev_channel
Linkedin: https://www.linkedin.com/in/dsysd-dev/
Newsletter: https://dsysd.beehiiv.com/subscribe
Gumroad: https://dsysd.gumroad.com/
Dev.to: https://dev.to/dsysd_dev/

Top comments (0)