2

I was looking the Rust's docs where they introduce this piece of code:

let guess: u32 = match guess.trim().parse() {
    Ok(num) => num,
    Err(_) => continue,
};

where the value num is assigned to guess in case of a successful parsing, otherwise the continue statement is executed making the loop to break.

Is it me or this syntax is a bit odd? It feels like we are executing guess = continue. I found this way of writing it more natural:

match guess.trim().parse() {
    Ok(num) => let guess: u32 = num,
    Err(_) => continue,
};

Am I understanding something wrong? I don't know anything about Rust, so maybe there is an explanation for this.

  • 2
    This is within a loop body, in which case `continue` will skip to the next iteration of the loop, skipping the rest of the loop body. This is essentially equivalent to `if (parse_result.is_err()) { continue }; let guess = parse_result.unwrap();`. – PitaJ Feb 18 '22 at 18:43
  • 5
    This is a common pattern in rust, which you're likely to see with `return`, `break`, etc. You can, in fact, write `let guess: u32 = return;` because the compiler knows that `return` can be used in place of any type, since we can never get to a point where we can use that value. – PitaJ Feb 18 '22 at 18:45

2 Answers2

5

match is (like amazingly many things in Rust) an expression. So at runtime it has a value that you can assign to a variable, for example. continue is a control statement. It causes the flow of control to resume at the next iteration of a loop. If that branch is taken, there is no need for a value because the assignment, which would be done after the match, is never executed.

If you are used to other programming languages that have exceptions the following idea may help understanding the situation... in Python, for example, you can assign the return value of a function to a variable:

value = func()

However, if func raises an exception there is no return value. None is needed because execution continues at the nearest exception handler.

Something similar is going on in Rust with control flow commands like continue, break, or return. They basically tell the computer to "forget what you are doing right now, continue with (or leave) the loop/function".


One could argue that the second way feels indeed more natural (it's how you would do it in Python, hehe). However, Rust has very strict scoping rules and it would be hard to define sensibly in which scope the guess variable should exist, or what would happen if another branch defined a different variable. The way it is done in Rust is very clear once you get used to it.

MB-F
  • 22,770
  • 4
  • 61
  • 116
4

What you have written is not only different from what was given in the example, it is also completely useless. match can either be used as an expression, or as a statement right away. In the first syntax, you are using match as an expression. Is it odd? Not if you are used to functional programming (for instance, you would have the same pattern in OCaml), but if you come from imperative language it can be a bit puzzling.

In the second case, this does not even mean anything because match arms should be expressions, not statements. So, to make it work, you should at least do something like

match guess.trim().parse() {
  Ok(num) => {
    let a = num;
  }
  Err(_) => continue,
}

But then, it's easy to understand what's wrong: the variable a has a lifetime that is less that the match scope, ie. the match scope outlives a, meaning that after the match statement, a is undefined.

See this playground example, where it just does what you expect, as opposed to this playground example which does not even compile. If you look, besides the "this does not make sense" error, there is a "this variable in undefined" error.

Besides, the idea of this pattern is that if you read the code quickly, you start by seeing a let pattern (which is a bit cumbersome), because that is the main point of that statement, and not a conditional flow instruction, because that is not the important part of that statement.

jthulhu
  • 7,223
  • 2
  • 16
  • 33