In this lesson we'll take a look at the simplest way of handling error in Rust using the unwrap()
function.
Instructor: [00:00] Let's say we have a function sum that takes two parameters, a and b, which are both of type number, and it simply adds those two numbers and returns them. We then want to ask the user to enter a first number and a second number. Eventually, we want to call sum with these two numbers and output the result.
[00:22] We read the first and the second number using stdin().read_line, but since first and second are both of type string, we can't pass them to sum because sum expects numbers. Let's say we would call sum with first and second. Save the file and run the program. We'll see that Rust won't be able to compile the program because the expected type is of u32, but what we've passed down was a string.
[00:56] To fix that, we create a new variable a of type u32. We first trim the input to make sure that there's no carriage returns and new lines. Then, we call a method parse() which tries to parse the string into a number, then we do the same thing for the second value.
[01:21] After that, we update the sum function to take a and b. When we run this, the compiler will still complain because we defined a and b to be type u32, however, parse returns something of type result. The result type is like a wrapper that either resolves with an error or a value.
[01:47] There's different ways to go about this. The easiest is to call a result::unwrap() method, which will simply say, "If this result does not emit an error, it will resolve with the value." However, otherwise, the program will panic. Let's call unwrap on both results and run the program again.
[02:09] The compiler still warns about the fact that the result of read_line is not handled, but we can ignore that for now. Now our program asks us to enter a first number and a second number. We'll see that it properly adds the numbers and tells us the result. If the parse function fails, unwrap will cause our program to panic.
[02:31] Let's run the program again and enter something that is not a number. Rust tells us that our program has panicked when we tried to unwrap a result. The cause of that was a parse_int error, which is the underline error which has been emitted from the parse function.
[02:48] It's very important to note that unwrap should only be used for quick developments. For production-ready code, errors should be handled differently.
Hi @Ya
you're absolutely right. This is a typo and also a typo in the video. Will update the embedded code!
I noticed you specified the type of a
and b
when defining them, in a way you hadn't in previous lessons or even for other declarations in this lesson. let a:u32 =
and let b:u32 =
vs. let mut first =
(which could also have been written let mut first:String =
). Was that just for making the code more readable, or did it serve some other purpose?
The code seems to run the same if I take those explicit types out, and hovering over them reveals that the editor has still inferred a type of u32
. Reviewing the description of parse
, it mentions that the method can handle many different types, so it gives you the option of casting the intended return type with this wild syntax (the "turbofish," apparently): parse::<u32>()
; but that isn't what you used, so I'm curious.
I also think it's interesting that the type is u32
rather than number
or int
or some such. I see there are other options like u128
; does it have to do with the complexity of the number in question?
Hey @Matthew,
excellent question again (keep them coming! :D).
Was that just for making the code more readable, or did it serve some other purpose?
So generally, the Rust compiler will infer the type of an expression if it can using Type Inference (I'll talk about it in a video collection about basic types that I'm working on right now). The line
let first = new String::new();
For example, doesn't need a type annotation, because the compiler knows that, when you create a String
using String::new()
, the type will be a String
.
In other cases however, Rust can't infer the type. That's the case in this line here:
let a = first.trim().parse().unwrap();
It turns out that parse()
is actually "generic" which means it can work on multiple types. If you're not telling the compiler what type you want to parse to, it can't know and will ask you to provide an annotation.
Take this example:
let val = "5"; // looks like a number but is actually a &str
let number = val.parse().unwrap();
This code will not compile because Rust can't know what, what we're aiming for here is a number type. A &str
(which is a string slice) can also be parsed to a String
, or any other number type in this case.
Doing
let number: i32 = val.parse().unwrap();
Tells the compiler that we're trying to parse to a signed integer of 32 bits. Another way to provide the type information is to use the turbo fish syntax:
let number = val.parse::<i32>().unwrap();
Which syntax you use in this case doesn't matter, but there are cases, especially when dealing with more functional code, where the return value of parse()
isn't the final value but gets passed to another function, where turbofish syntax is necessary.
For the sake of this lesson I didn't want to make it more complex than needed, but decided to cover this in another lesson that I can then link here.
The code seems to run the same if I take those explicit types out, and hovering over them reveals that the editor has still inferred a type of u32. Reviewing the description of parse, it mentions that the method can handle many different types, so it gives you the option of casting the intended return type with this wild syntax (the "turbofish," apparently): parse::<u32>(); but that isn't what you used, so I'm curious.
Ha, I should've finished reading your comment first before responding! :D You got it!
I also think it's interesting that the type is u32 rather than number or int or some such. I see there are other options like u128; does it have to do with the complexity of the number in question?
Also something I'll cover in the collection I'm working on right now but the bottom line is that there are signed and unsigned integers of different fixed sizes:
u8
- Unsigned 8 bit integer
u16
- Unsigned 16 bit integer
u32
- Unsigned 32 bit integer
... and so on.
i8
- Signed 8 bit integer
i16
- Signed 6 bit integer
... you get the idea.
Depending on how big the values are you want to store, you might wanna use one type over the other. Rust defaults to i32
if none is given.
Hope this makes sense!
Which syntax you use in this case doesn't matter, but there are cases, especially when dealing with more functional code, where the return value of
parse()
isn't the final value but gets passed to another function, where turbofish syntax is necessary.
Ah, I see, so specifying the type in the declaration does do the same thing that turbofish would have done, at least in this case. That makes sense, as does the rest of your great response. Thanks!
line 14 should set
b:u32
tosecond.trim().parse().unwrap()
right?