Timothy Alexis Vass
Timothy Alexis Vass

Reputation: 2705

"Borrowed value does not live long enough", dropped when used in a loop

I understand that the String is dropped when the scope of the loop ends and that the vector input contains slices of trimmed_text.

I suppose the resolution is to move the ownership of those slices to input or something like that. How can this be done?

use std::io;

fn main() {
    let mut input: Vec<&str>;

    loop {
        let mut input_text = String::new();
        println!("Type instruction in the format Add <name> to <department>:");
        io::stdin()
            .read_line(&mut input_text)
            .expect("failed to read from stdin");
        let trimmed_text: String = input_text.trim().to_string();

        input = trimmed_text.split(" ").collect();

        if input[0] == "Add" && input[2] == "to" {
            break;
        } else {
            println!("Invalid format.");
        }
    }

    println!("{:?}", input);
}

The compile error:

error[E0597]: `trimmed_text` does not live long enough
  --> src/main.rs:14:17
   |
14 |         input = trimmed_text.split(" ").collect();
   |                 ^^^^^^^^^^^^ borrowed value does not live long enough
...
21 |     }
   |     - `trimmed_text` dropped here while still borrowed
22 | 
23 |     println!("{:?}", input);
   |                      ----- borrow later used here

Upvotes: 4

Views: 8283

Answers (2)

Naeio
Naeio

Reputation: 1181

As you did understand, the problem is that the owner of the slices doesn't live long enough. What you didn't point out (and maybe did not explicitly see) is that the owner is the variable trimmed_text. So what you want to do (if you don't want to copy each slice to have better performances) is to make trimmed_text's scope bigger:

use std::io;

fn main() {
    let mut input: Vec<&str>;
    let mut trimmed_text: String;

    loop {
        ...
        trimmed_text = input_text.trim().to_string();

        input = trimmed_text.split(" ").collect();

        if input[0] == "Add" && input[2] == "to" {
            break;
        } else {
            ...
        }
    }

    println!("{:?}", input);
}

Here, we resolved the error of the lifetime of the owner of the slices. However, we have a second issue:

13 |         trimmed_text = input_text.trim().to_string();
   |         ^^^^^^^^^^^^ assignment to borrowed `trimmed_text` occurs here
14 | 
15 |         input = trimmed_text.split(" ").collect();
   |         -----   ------------ borrow of `trimmed_text` occurs here
   |         |
   |         borrow might be used here, when `input` is dropped and runs the `Drop` code for type `std::vec::Vec`

This tells us that after a loop, the input variable still borrows trimmed_text as it is modified. To fix this, we can reduce the scope of input so that input's scope doesn't contain the line 13 :

use std::io;

fn main() {
    // Remove the "let mut input: Vec<&str>;"
    let mut trimmed_text: String;

    loop {
        ...
        trimmed_text = input_text.trim().to_string();

        let input: Vec<&str> = trimmed_text.split(" ").collect();

        ...
    }
}

If we don't use input outside of the loop, this works just fine. Now we just need to output the value of input at the end of the loop :

use std::io;

fn main() {
    // Remove the "let mut input: Vec<&str>;"
    let mut trimmed_text: String;

    let results = loop {
        ...
        trimmed_text = input_text.trim().to_string();

        let input: Vec<&str> = trimmed_text.split(" ").collect();

        if input[0] == "Add" && input[2] == "to" {
            break input;
        }
        ...
    };

    println!("{:?}", results);
}

And here we have it !

TL;DR:

Here is a code that does what you want without duplicating the slices :

use std::io;

fn main() {
    let mut trimmed_text: String;

    let results = loop {
        let mut input_text = String::new();
        println!("Type instruction in the format Add <name> to <department>:");
        io::stdin()
            .read_line(&mut input_text)
            .expect("failed to read from stdin");
        trimmed_text = input_text.trim().to_string();

        let input: Vec<&str> = trimmed_text.split(" ").collect();

        if input[0] == "Add" && input[2] == "to" {
            break input;
        } else {
            println!("Invalid format.");
        }
    };

    println!("{:?}", results);
}

I renamed input to results to avoid confusion with the input variable inside the loop. Feel free to rename it back to input if you really want to.

Upvotes: 1

pretzelhammer
pretzelhammer

Reputation: 15105

.split() returns references to a String which is dropped by the end of the loop, but you want input to live past the end of the loop, so you should refactor it to hold owned values instead of references. Example:

use std::io;

fn example() {
    let mut input: Vec<String>; // changed from &str to String

    loop {
        let mut input_text = String::new();
        println!("Type instruction in the format Add <name> to <department>:");
        io::stdin()
            .read_line(&mut input_text)
            .expect("failed to read from stdin");

        // map str refs into owned Strings
        input = input_text.trim().split(" ").map(String::from).collect();

        if input[0] == "Add" && input[2] == "to" {
            break;
        } else {
            println!("Invalid format.");
        }
    }

    println!("{:?}", input);
}

playground

Upvotes: 9

Related Questions