How to load and compute data properly from stdin

I am reading multiple lines from stdin.
I want store those lines into a Vec<String> and then make use of it with references.

Here a simplified code template:

    let mut pretty_line: &str;
    let mut lines: Vec<String> = Vec::new();
    for i in 0..lines_number as usize {
        let line = // read line

        lines.push(line);

        if is_pretty_line(&line) {
            pretty_line = &line;
        }
    }

Since line would be dropped at the end of the loop I thought of storing it into a Vec to be sure it still exists. But I now end up with borrowing issues because value is moved when lines.push(line);

I thought of :

 if is_pretty_line(&lines[i]) {
    pretty_line = &lines[i];
 }

But not possible either because there is a mutable borrow when pushing to the Vec.
How can I solve this ?

Something interesting if I remove the lines.push(line); there is no more compilation error even though I am assigning a reference to a String that will be dropped after :pretty_line = &line;

How is that possible ?

Full code :

use std::io;

fn main() {
    let mut input_line = String::new();
    io::stdin().read_line(&mut input_line).unwrap();
    let lines_number = input_line.trim().parse::<i32>().unwrap();

    let mut pretty_line: &str;
    let lines: Vec<String> = Vec::new();
    for _i in 0..lines_number as usize {
        let mut line = String::new();
        io::stdin().read_line(&mut line).unwrap();
        let line = input_line.trim().parse::<String>().unwrap();

        lines.push(line);

        if is_pretty_line(&line) {
            pretty_line = &line;
        }
    }
}

fn is_pretty_line(line: &str) -> bool {
    return true;
}

Instead of storing a reference to the vec element, consider storing its index so that you can retrieve it later without borrowing the vec in the meantime

Using an index to store the pretty_line_idx would solve it.

Your code is technically valid as the String data is heap allocated and reallocating lines does not affect it.
However rustc does not know about that and works on the assumption that lines.push(line) can do anything.

If you're just storing all the lines anyway, and you don't need pretty_line inside the loop, you can just wait to look up the last pretty line until after the loop.

    // After the for loop, if `lines` is going to change anymore
    let pretty_line = lines
        .iter()
        .filter(|s| is_pretty_line(s))
        .last()
        .expect("No lines were pretty");

You don't use pretty_line after the loop, and it doesn't have a Drop implementation. This allowed the compiler to rearrange the lifetimes to work.

If you add something like println!("{}", pretty_line) after the loop, you'll see a borrow error -- and also an error that pretty_line may be unitialized, as there is no guarantee the if block will execute, or that the loop body as a whole will execute for that matter.

2 Likes

I agree that using pretty_line_idx would be better in this case. But in a more complex use case I really need to store a reference to a line that I am storing

If you can't rearrange it so that lines is not mutated anymore before you create the reference, Rc<String> or Rc<str> is probably your only safe solution.

Thanks it is clear now why there is no compilation errors when removing the push operation.

That's true that I need pretty_line only after the loop, but if I search it after the loop I have to do an additional O(n) operation where I could have find it while storing it.