[Solved] Storing user input in loop?

#1

Hello Rust Community!

As an exercise in learning Rust I am trying to make a simple console program. I should be able to acquire data, store and modify it by user command.

I have run into troubles with the second part. Somehow, when trying to store user input, I am running into “borrowed value does not live long enough” error.

Below is a program snippet that reproduces the problem:

    use std::io;
    use std::collections::HashMap;

    fn main() {

        // Structure to store user input in during loop
        let mut map: HashMap<&str, &str> = HashMap::new();

        loop {
            
            // User input should be a command of several words,
            // like "add Alice to Wonderland"
            let mut input = String::new();

            io::stdin().read_line(&mut input)
                .expect("Error reading input");
            
            // The input string is separated to individual words vector
            // like "add" "Alice" "to" "Wonderland"
            // to simplify further processing
            let mut input: Vec<&str> = input.split_whitespace().collect();
            println!("{:?}", input);

            // No input ends cycle and finishes program
            if input.is_empty() { break }

            // Here:
            // Trying to store value in HashMap causes lifetime error
            map.entry(input[1]).or_insert(input[3]);
        }
    }

And the subsequent compilation error:

    error[E0597]: `input` does not live long enough
      --> src/main.rs:18:36
       |
    18 |         let mut input: Vec<&str> = input.split_whitespace().collect();
       |                                    ^^^^^ borrowed value does not live long enough
    ...
    24 |         map.entry("some_key").or_insert(input[0]);
       |         --- borrow used here, in later iteration of loop
    25 |     }
       |     - `input` dropped here while still borrowedThis text will be hidden

I have been looking for solution, but couldn’t find the case of processing user input in loop, while storing it in a permanent structure.

P.S. Also, how do I highlight syntax here?

#2

References are not good for storage (except extreme performance cases.) Replace &str with String. A few other changes then that you may be able to figure out; if not post again.

Use ``` around code for highlighting.

2 Likes
#3

Great, that works!

Thank you - I’ve spent several hours trying to figure out what to do. Solution turned out to be very logical :slight_smile:

Here is the modified code, in case someone would run into the same conceptual issue in the future:

use std::io;
use std::collections::HashMap;

fn main() {

    // Structure to store user input in loop
    let mut map: HashMap<String, String> = HashMap::new();

    loop {

        // User input should be a command of several words,
        // like "add Alice to Wonderland"
        let mut input = String::new();

        io::stdin().read_line(&mut input)
            .expect("Error reading input");

        // The input string is separated to individual words vector
        // like "add" "Alice" "to" "Wonderland"
        // to simplify further processing
        let mut input: Vec<&str> = input.split_whitespace().collect();
        println!("{:?}", input);

        // No input ends cycle and finishes program
        if input.is_empty() { break }

        // UPD: replaced &str with String
        map.entry(String::from(input[1])). // Name
            or_insert(String::from(input[3])); // Department
    }
}
#4

You can use this:

let input: Vec<_> = input.split_whitespace()
   .map(|s| s.to_string()).collect();

.map gives you Strings right away. You don’t need to write the full type, .collect() only needs a hint you want a vec.

There’s more micro-optimizations you could make, e.g.

let mut iter = input.split_whitespace().map(|s| s.to_string()); // no collect

iter.next(); // skip 0
let name = match input.next() { Some(name) => name, None => break };
iter.next(); // skip 2

map.entry(name)
  .or_insert_with(|| iter.next().expect("dept should be there"));

This takes strings from the iterator only when needed, instead of splitting them all first.

2 Likes
#5

Thank you for pointing those things out!

The second approach with the request-on-demand iterator should be really useful for the long sequences of data.