A First Look at Lifetimes in Rust

A new video from that YouTube channel whose previous introduction video to Rust (“A Firehose of Rust, for busy people who know some C++”) I had enjoyed a lot.

This one doesn’t disappoint, either. As with the other one, I appreciate the attention to detail and accuracy, whilst keeping it pragmatic and focusing on the most important aspects only.[1]

Of course, this is beginner / introduction level, so if you’re more experienced, this kind of material is perhaps only interesting for coming up with ways to teach Rust better, or as a concrete resource to link for others who could learn from it.


  1. Too many other teaching resources one can find on YouTube rely on bad oversimplification, or come from creators nor too experience in the language themselves, which can result in teaching misleading information, which can lead to confusing down the line at least for those kinds of viewers who happen to commit some of the misleading bits to memory. ↩︎

14 Likes

I was impressed as well. A very good way to give a good intuition on what the compiler does (and do not do). Very concise. Great video :+1:

1 Like

Hi @steffahn,

Thank you for the video, it's very educational. In the past few weeks, I have encountered this issue twice using the actix-web framework, especially with closures.

There are a lot of problem questions on the same subject in the web.

I am studying this Stack Overflow post error [E0716]: temporary value dropped while borrowed (rust). Basically this is the problem code:

type Vec1<'a> = Vec::<&'a String>;

fn fun1(s1: &String, v1: &mut Vec1) {
    v1.insert(0, &s1.clone());
}

fn main() {
    let mut vec1 = Vec::new();
    let str1 = String::new();
    fun1(&str1, &mut vec1);
}

The compiling error is (I compiled it):

error[E0716]: temporary value dropped while borrowed
  --> src\std_019_dropped_borrowed.rs:11:19
   |
10 | fn fun1(s1: &String, v1: &mut Vec1) {
   |                      -- has type `&mut Vec<&'1 String>`
11 |     v1.insert(0, &s1.clone());
   |     --------------^^^^^^^^^^-- temporary value is freed at the end of this statement
   |     |             |
   |     |             creates a temporary value which is freed while still in use
   |     argument requires that borrow lasts for `'1`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0716`.

While both solutions in the answer work. The second solution removes the function fun1:

type Vec1<'a> = Vec::<&'a String>;

fn main() {
    let mut vec1 = Vec::new();
    let str1 = String::new();
    let clone = str1.clone();
    vec1.insert(0, &clone);
    println!("{:?}", vec1);
}

I try to understand it better by reinstating fun1, I got stuck:

type Vec1<'a> = Vec::<&'a String>;

fn fun1(s1: &String, v1: &mut Vec1) {
    v1.insert(0, s1);
}

fn main() {
    let mut vec1 = Vec::new();

    let str1 = String::from("abcde");
    let clone = str1.clone();

    fun1(&clone, &mut vec1);

    println!("Vector {:#?}", vec1);
}

The error is:

error: lifetime may not live long enough
 --> src\std_019_dropped_borrowed_b.rs:4:5
  |
3 | fn fun1(s1: &String, v1: &mut Vec1) {
  |             -        -- has type `&mut Vec<&'2 String>`
  |             |
  |             let's call the lifetime of this reference `'1`
4 |     v1.insert(0, s1);
  |     ^^^^^^^^^^^^^^^^ argument requires that `'1` must outlive `'2`

error: aborting due to previous error

This tutorial video enables me to figure out what the problem is. After the video, I changed to code to:

type Vec1<'a> = Vec::<&'a String>;

fn fun1<'a>(s1: &'a String, v1: &mut Vec::<&'a String>) {
    v1.insert(0, s1);
}

fn main() {
    let mut vec1 = Vec1::new();

    let str1 = String::from("abcde");
    let clone = str1.clone();

    fun1(&clone, &mut vec1);

    println!("Vector {:#?}", vec1);

    // I expect both str1, and clone are still available:
    println!("str1: {}", str1);
    println!("clone: {}", clone);
}

Best regards,

...behai.

1 Like

Note that maybe the .clone() call in the function is merely supposed to be a stand-in and supposed to represent the general idea of "what if I need to transform the string into an actual new one" (though better examples would be methods like to_lowercase for example). If you still want to have such a transformation.. well and keep it part of fun1 as well, then your solution no longer works.

Interestingly, there are other ways to solve the issue then, still. One could manually pass in also some longer-lived place to store the new String.

You could try this as a challenge. Use s1.to_lowercase() instead instead of just s1, and keep the transformation within fun1. Find a way to still add the &String reference to the Vec. To do so, pass an additional place: &mut Option<String> value, and call the method by first creating a let mut place = None; variable and the passing it fun1(&str1, &mut vec1, &mut place). Find a way to put the newly created String into that Option and then take a reference into it. Hint: There's a useful method called Option::insert. Then also make sure that you equate the right lifetimes to make the signature work correctly.

As a follow-up, you could even replace the &mut Option<String> with something more dynamic like &typed_arena::Arena<String>, and that would allow more flexible usage, even the ability to call fun1 many times in a loop.

3 Likes

Hi @steffahn,

Thank you for the further information. I have tested out your above point. This is the code that I tried:

type Vec1<'a> = Vec::<&'a String>;

fn fun1<'a>(s1: &'a String, v1: &mut Vec::<&'a String>) {
    v1.insert(0, s1.to_lowercase());
}

fn main() {
    let mut vec1 = Vec1::new();

    let str1 = String::from("ABCDE");
    // let clone = str1.clone();

    fun1(&str1, &mut vec1);

    println!("Vector {:#?}", vec1);
}

The error is:

error[E0308]: mismatched types
    --> src\std_019_dropped_borrowed_challenge.rs:33:18
     |
33   |     v1.insert(0, s1.to_lowercase());
     |        ------    ^^^^^^^^^^^^^^^^^ expected `&String`, found `String`
     |        |
     |        arguments to this method are incorrect
     |
note: method defined here
    --> C:\Users\behai\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib/rustlib/src/rust\library\alloc\src\vec\mod.rs:1490:12
     |
1490 |     pub fn insert(&mut self, index: usize, element: T) {
     |            ^^^^^^
help: try removing the method call
     |
33   -     v1.insert(0, s1.to_lowercase());
33   +     v1.insert(0, s1);
     |

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.

Which is understable since method to_lowercase return a new String rather a reference to a String. Referencing the returned String:

fn fun1<'a>(s1: &'a String, v1: &mut Vec::<&'a String>) {
    v1.insert(0, &s1.to_lowercase());
}

Resulted in the "original" error E0716:

error[E0716]: temporary value dropped while borrowed
  --> src\std_019_dropped_borrowed_challenge.rs:91:19
   |
90 | fn fun1<'a>(s1: &'a String, v1: &mut Vec::<&'a String>) {
   |         -- lifetime `'a` defined here
91 |     v1.insert(0, &s1.to_lowercase());
   |     --------------^^^^^^^^^^^^^^^^^-- temporary value is freed at the end of this statement
   |     |             |
   |     |             creates a temporary value which is freed while still in use
   |     argument requires that borrow lasts for `'a`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0716`.

I will work through the points you mentioned. I have not looked at the hint yet :slight_smile:

Thank you and best regards,

...behai.

Hi @steffahn,

I think I have done this point? I only at looked your hint when writing this answer. I did not
peek at it before hand:

type Vec1<'a> = Vec::<&'a String>;

fn fun1<'a>(s1: &'a String, v1: &mut Vec::<&'a String>, place: &'a mut Option<String>) {
    *place = Some(s1.to_lowercase());
    
    let str: &String = &place.as_ref().unwrap();

    v1.insert(0, str);
}

fn main() {
    let mut vec1 = Vec1::new();

    let str1 = String::from("ABCDE");

    let mut place = None;

    fun1(&str1, &mut vec1, &mut place);

    // println!("place = {:#?}", place);

    println!("Vector {:#?}", vec1);
}

I did not get *place in the first place. The compiler suggested that.

I did not &place.as_ref().unwrap(); right in the first place either, the compiler also suggested as_ref().

I understand that this code works, because we hang on to str1.to_lowercase() in place.

I can only either println!("place = {:#?}", place); or println!("Vector {:#?}", vec1);, but not both at the same time: I have not encountered this before.

I do both, the errors are:

error[E0502]: cannot borrow `place` as immutable because it is also borrowed as mutable
  --> src\ss.rs:20:31
   |
18 |     fun1(&str1, &mut vec1, &mut place);
   |                            ---------- mutable borrow occurs here
19 |
20 |     println!("place = {:#?}", place);
   |                               ^^^^^ immutable borrow occurs here
21 |
22 |     println!("Vector {:#?}", vec1);
   |                              ---- mutable borrow later used here
   |
   = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to previous error

For more information about this error, try `rustc --explain E0502`.

Thank you and best regards,

...behai.

2 Likes

Almost correct. But the function signature is now slightly overly restrictive. It's easy to mark too many things with the same lifetime (a point also made in the video), especially when working it out incrementally like that. Since you don't put the reference s1 into the vector directly anymore, but just read its data for a shorter time for the to_lowercase call, there is no need anymore to prescribe it to have the same lifetime 'a as the vector elements and the place have.

2 Likes

Thank you very much. I missed this point altogether. I have tried this:

fn fun1<'a>(s1: &String, v1: &mut Vec::<&'a String>, place: &'a mut Option<String>)

And as you have pointed out, it works as before.

Thank you and best regards,

...behai.

Hi,

I think I have completed this point. We are using the typed-arena crate.

The [dependencies] section of Cargo.toml is:

...
[dependencies]
typed-arena = "2.0"

Content of src/main.rs is:

use typed_arena::Arena;

type Vec1<'a> = Vec::<&'a String>;

fn fun1<'a>(s1: &str, v1: &mut Vec::<&'a String>, arena: &'a Arena<String>) {
    v1.insert( v1.len(), arena.alloc(s1.to_lowercase()) );
}

fn main() {
    let mut vec1 = Vec1::new();

    let arena = Arena::<String>::new();

    let words: Vec<&str> = "THE RAIN IN SPAIN STAYS MAINLY IN THE PLAIN".split(" ").collect();

    for s in words {
        fun1(s, &mut vec1, &arena);
    }

    println!("Resulting vector {:#?}", vec1);

    let vec2 = arena.into_vec();
    for s in vec2.iter() {
        print!("{} ", s);
    }
}

The output is:

Resulting vector [
    "the",
    "rain",
    "in",
    "spain",
    "stays",
    "mainly",
    "in",
    "the",
    "plain",
]
the rain in spain stays mainly in the plain

Originally, I had it at:

fn fun1<'a>(s1: &'a str, v1: &mut Vec::<&'a String>, arena: &'a Arena<String>)

But based on your last reply, I was able to see that we could remove the lifetime annotation for parameter s1.

Thank you very much for kindness, for the times you have given me.

Best regards,

...behai.

3 Likes