Populating variables in a loop that need to be accessible outside the loop

Hey all,

New to Rust; first post here.

I'm trying to find how I should approach the following scenario:

  • Reading a text file line by line.
  • If I find token first_name=John, save "John" for later.
  • If I find token last_name=Doe, print first_name and last_name, i.e. "John Doe".

(One of) The "gotchas" is:

  • In a malformed input file, I might hit last_name before first_name.

I know that this code is awful in many ways, but the concept I'm trying to get my head around is declaring variables that will get populated later, but might not (because of malformed input).

Specifically, my questions are:

  1. At ??? 1 ???, how do/should I declare, but not initialize the variable?
  2. Am I thinking about this all wrong?

I currently have this:

// Read the file, line by line
let reader = BufReader::new(file);
let mut first_name... // ??? 1 ???, in python: first_name = None

for line in reader.lines() {

    let line = match line {
        Ok(line) => line,
        Err(error) => {
            println!("Error reading line from file: {:?}", error);
            process::exit(1);
        }
    };

    if line.contains("first_name") {
        println!("found first_name: {}", line);
        first_name = line.replace("first_name=", "");
    }

    if line.contains("last_name") {
        println!("found last_name: {}", line);
        let last_name = line.replace("last_name=", "");
        if first_name... // python: is not none:
        println!("name: {} {}", first_name, last_name);
    }
}

It doesn't feel right, because I suspect Rust will demand I am more safe than this, but I can't think how to be.

Any pointers greatly appreciated!
Thanks!

Welcome!

The most straightforward way to do something like this in Rust is to use the Option type (a more thorough explanation of the Option type is found in the book). Option is an enum type, which can either contain some value, or nothing. With Option, you can initialize first_name like so

let mut first_name = None;

Then, when you later want to set it to some actual value you need to wrap it in Some():

first_name = Some(line.replace("first_name=", ""));

There are a couple ways to then check and use first_name if it has some value inside, but the most straightforward in this case is with if let:

if let Some(first_name) = first_name.take() {
    println!("name: {} {}", first_name, last_name);
}

The syntax can be a bit strange and look backwards at first, but it'll hopefully start making sense as you get more familiar with the language. In short, if let tries to fit the expression on the right first_name.take() into the pattern on the left Some(first_name). If it fits, i.e. if first_name contains some value, the inner block is executed and the inner String is usable as first_name inside it. The .take() method call takes the inner value out of first_name and sets it back to None, which is probably what you want in this case.

An extra note: I used the same variable name first_name in the if let, but you can use anything you like, this works just as well:

if let Some(you_can_use_any_name_you_like_here) = first_name {
    println!("name: {} {}", you_can_use_any_name_you_like_here, last_name);
}

I recommend checking out the book I linked earlier if you haven't had a chance to do so yet: https://doc.rust-lang.org/book/
Let me know if anything is unclear.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.