Handle errors in Rust using Pattern Matching

Share this video with your friends

Send Tweet

In this lesson we'll explore how to unwrap a Result type using a language feature called Pattern Matching.

J. Matthew
J. Matthew
~ 5 years ago

Several questions here:

  1. I noticed that inside match, you're using the fat arrow => for the Ok and Err code blocks, which presumably is doing something that is not immediately clear to me. Previously all we saw was the thin arrow ->, which is still used here in the sum function definition, and which seems to be indicating the function's return type. What is the difference between and the meaning behind the two arrow types?

(I've worked a little with CoffeeScript, and I recall that it too uses both fat and thin arrows, which function similarly but bind different things to this, while ES6 JavaScript has only the fat arrow, which also handles this binding in place of an explicit bind call, but I get the feeling the purpose of the arrows is different here.)

  1. Speaking of that match syntax, what exactly is going on in there? It looks like the outermost braces are wrapping a code block, but then inside it looks like an object, with Ok and Err properties that are lambdas. Or something. To be clear, I get that the value returned by parse is triggering (and being passed to) one of the two functions inside the block; it's just that the way all of that is written looks a little unusual. It does remind me strongly of the switch statement in JavaScript; is the structure and concept comparable?

  2. Speaking of parse, I noticed that now that we've pulled it out of the variable definition that previously supplied the intended type, and we're not using the turbofish syntax instead, so I would expect type inference to fail. Yet the code still compiles without issue. I also noticed that if I comment out a = val;, then I get the expected type inference failure. Am I correct then that the compiler infers the type via the assignment that occurs inside the Ok function? That's pretty crazy, and impressive.

  3. I noticed that when I run the code, I get two warnings about the read_line lines "not being used," but you only get one such warning in the video. Studying the video and the embedded code below it more closely, I discovered that when you're reading in first in the video, you then chain unwrap at the end, but not so when reading in second, which is what throws your one warning. And in the embedded code, both read_line instances chain unwrap. And, sure enough, if I do the same, the warnings disappear. I don't recall you saying anything about adding unwrap to either of those lines, though; was that just a quick-and-dirty way to get rid of the warnings, or is something more going on there?

Pascal Precht
Pascal Precht(instructor)
~ 5 years ago

Hi Matt,

  1. Yes, good point. I didn't make that very clear in the video. You're spot on that -> is used to annotate the return type of a function/method. The fat arrow is just the syntax for match branches. So doing:
match some_expr {
  Ok(val) => {
    ...
  },
  Err(e) => {
    ...
  }
}

Really just indicates the body of the match branch. That also why, in case of single-line branch body's, they can be omitted:

match some_expr {
  Ok(val) => ...
  Err(e) => ...
};

Notice however that, when a match branch is only a single-line expression, the value of that expression will be the returned value from that match expression.

  1. It does remind me strongly of the switch statement in JavaScript; is the structure and concept comparable? Yes! Very correct. You can think of match pattern as switch statements on steroids. Match pattern are way more powerful though, because they support a variety of pattern syntaxes that let you express with a lot of fine-control what branch should match. I'll create a collection for pattern matching in the near future to dive more into that!

  2. I'm not entirely sure I follow the question on type inference here. If you look at the code we and up with

 let mut a:u32 = 0;
    match first.trim().parse() {
        Ok(val) => a = val,
        Err(_err) => {
            println!("Not a valid number!");
        }
    };

Putting parse() into a match expression doesn't change the fact that the function is generic and therefore need to be explicitly annotated. However, since we have an annotation on a: u32 and b: u32, and we're assigning val to both a and b respectively, Rust is smart enough to see that we're aiming to parse to u32. Does that make sense?

  1. I don't recall you saying anything about adding unwrap to either of those lines, though; was that just a quick-and-dirty way to get rid of the warnings, or is something more going on there?

Yea so that was a little mistake on my side. unwrap() was covered in a previous lesson, and for the sake of the lesson I've only updated it in one place (but should've happened in both places). So the right thing to do (for completeness-sake) would be to apply the unwrap() on both read_line() calls.

J. Matthew
J. Matthew
~ 5 years ago

Cool, thanks for all that!

  1. I'm not entirely sure I follow the question on type inference here. If you look at the code we and up with...

I think you answered what I was trying to ask.

  1. So the right thing to do (for completeness-sake) would be to apply the unwrap() on both read_line() calls.

I'm still a little confused on this, only because you introduced expect as a better solution than unwrap, and then match as a better solution than expect, so it was surprising to see unwrap still present. And it's chained onto the read_line calls where it's not clear that it's doing anything. So I was just curious if you only added it so that you wouldn't get the warning about those read_line calls being "unused."

Pascal Precht
Pascal Precht(instructor)
~ 5 years ago

Hey Matt,

I'm still a little confused on this, only because you introduced expect as a better solution than unwrap, and then match as a better solution than expect, so it was surprising to see unwrap still present.

Very fair point. I wasn't clear about that at all.

So yes, the read_line() call in itself returns a Result<T, E> as well, however, we're not actually interested in that. In this case, we're only interested in it to mutate our String buffer. So in order to satisfy the compiler, we unwrap() the Result returned from read_line() and ignore what it returns (it'll still mutate the buffer though).

Hope this makes sense!