Rust Lifetime Annotations Explained

If you're like me, you may have walked away after reading about lifetime annotations understanding their purpose but not really getting why the developer needs to manually annotate them in many situations. I'll provide a quick example that clearly shows why the compiler really needs you to specify this info.

This article is not a comprehensive tutorial on lifetimes in Rust. I'll assume you have already gone over the basics of lifetimes and how to annotate them. Reading through this section in the official Rust book would be a good start.

First, let's take a look at some code that wouldn't even compile because lifetimes have not been specified. This is important because we're going to think through possible scenarios and see why the compiler can't deduce anything without some help from you. Remember that the Rust compiler does not look at the bodies of functions when compiling to determine correctness. It needs to have all the info it needs from the function signature (where lifetimes are specified). With that info out of the way, take a look at this code:

Should main compile? The answer is that the compiler doesn't know. It only looks at the function signature and, from what is provided, it doesn't know if the reference being returned from do_stuff in line 17 is tied to n1 or n2. If the result is tied to n1, we would be okay to compile since n1 is in the same scope as result. But what if the result is tied to n2? n2 goes out of scope once the inner scope is done on line 18 so we would then be trying to return a reference to some memory that has been freed. So now it becomes clear that the compiler needs you to specify more info so that it can determine the correctness of your code.

Let's now take a look at code where the lifetimes have been properly annotated:

This code will indeed a compile, and you have given the Rust compiler the information it needs to determine correctness. By giving the result being returned by do_stuff a lifetime annotation of 'a (which is the same lifetime as its first argument) and specifying a different lifetime for the second argument, you have given the compiler the information it needs to know that it is safe to compile. This info lets the compiler know even when n2 goes out of scope (line 17) that it's okay because the lifetime of the result being returned from do_stuff doesn't depend on the lifetime of n2.