error [E0716]: temporary value dropped while borrowed (rust)
Asked Answered
D

3

3

tl;dr error[E0716]: temporary value dropped while borrowed is a difficult and common problem, is there a consistent solution?


I've run into the difficult rustc error

error[E0716]: temporary value dropped while borrowed
...

creates a temporary which is freed while still in use

Searching Stackoverflow, there are many questions for this rust error error[E0716]. Maybe a rust expert can provide a general solution for this common newbie problem, a solution good enough that it might also Answer the linked Questions (see below).

example code

A concise code sample to demonstrate the problem (rust playground):

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);
}

result:

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

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

my understanding

My understanding is given the statement v1.insert(0, &s1.clone());,
the s1.clone() would create a new String using the heap as storage. Then a reference of that newly cloned String (the added &) is passed into call v1.insert. So the new String data and the reference passed to insert will remain after the function fun1 returns.

But the rust compiler reports s1.clone() is merely temporary.

similar linked questions

Here are links to similar Questions, not always the same, version of this Question, but somewhat more cumbersome and verbose (IMO).

I added a Comment on those Questions that links to this Question.

Deanndeanna answered 26/3, 2022 at 7:20 Comment(2)
best advice is "DONT USE LIFETIME IN RUST" until you use rust for at least a yearPteropod
please do not spam comments on other questions to advertise yours, just mentioning them in this post is enough to associate them (the "linked" section on the right)Square
M
5

Your problem is indeed at the line indicated by the compile. Let's analyze it a little bit:

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

Your understanding is not quite correct. Let's look at the insert's signature:

pub fn insert(&mut self, index: usize, element: T)

The type T means that it captures by value, so element won't be used after the call. You just tweaked this by making the vector a Vec<&String> not a Vec<String>.

Outcome: You want to insert a reference to a clone of the string

How it is done: you clone and insert the the reference

The difference between rust's reference and C/C++ pointers is that rust doesn't allow reference to point to deallocated data (they must always point to valid data). When you clone the string, you create a carbon copy of it, but that carbon copy is available only for the lifetime of fun1. When the call to fun1 ends, the cloned string will be dropped, because the function owned that clone, so it is responsible for cleaning it (rust's ownership based resource management).

In C++ that would have been valid: you could've allocated a pointer and push that pointer into the vector, but rust does not allow such things.

Your fun1 is equivalent to this one:

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

Similar operation should always ring a bell becase im_tmp will be cleaned. To fix your issue:

type Vec1<'a> = Vec::<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);
    println!("{:?}", vec1);
}

The type is no longer a vector of references, but instead the actual object. In fun1, the vector takes ownership of the cloned string.

Another example where your vector has the type you created, is this one, but this works because the compiler can infer the lifetime of the cloned string. Notice, if you do vec1.insert(0, &str1.clone()) won't work , because tha reference to the clone will be available only for the lifetime of the call to the insert:

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);
}
Meetinghouse answered 26/3, 2022 at 8:2 Comment(1)
Thanks @AlexandruPlacinta . I ended up using String. I may look into @NicholasWeston Answer that suggested using a smart pointer. In my case, the String value will never change and remains for the lifetime of the program, so a Box pointer should be adequate.Deanndeanna
R
2

s1.clone() may involve some heap allocation behind the scenes, but ultimately, it also involves memory stored on the stack. For example, the length of the string and its pointer are stored on the stack. Once the method exits, this data is dropped.

If the Rust compiler didn't prevent you from doing this, v1 would contain a reference to the value created by s1.clone(), which would be dropped after the method exists. This is a problem, because v1 would contain a reference to data that is no longer valid.

To answer your more general question about how to avoid this error (and similar errors):

  • As soon as you have explicit lifetime parameters – unless you have very good reason to do so and know exactly what you're doing – you're likely heading down a rabbit hole and will up in an endless tango with the borrow checker.
  • Learn about smart pointers, which are (in general) necessary to allocate values on the heap.

This article expresses a similar view.

Raffish answered 26/3, 2022 at 8:6 Comment(1)
Thanks @NicholasWeston . In my case, the String value never changes and lasts for the remaining lifetime of the program. A Vec<Box<String>> should be sufficient.Deanndeanna
G
-1

Based on the second example in the answer by Alexandru Placinta, and this tutorial video A First Look at Lifetimes in Rust, I am able to come up with this version:

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);
}
Giselegisella answered 28/2 at 3:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.