The Result type of Rust

  • Original link: medium.com/joekreydt/…
  • Originally by Joe Kreydt
  • Translator: suhanyujie
  • Post collation: Zhang Handong

The Result type is a common and flexible method type used in Rust to handle errors. It should be very flexible!

Result may not be intuitive to those who are learning Rust, but it’s a good idea to read its standard library documentation to see how to use it. That’s fine if you want to learn it urgently, but if you just use it to handle errors or use a function that returns Result (as many people do), you probably won’t appreciate it.

To save you time, I’m going to use English to explain the Result type of Rust.

What is Result?

referenceRust Authoritative Guide

“Result is the likelihood of an error. Usually errors are used to explain why some task failed.”

Explain in plain English

Result is the type returned by a function, which can be Ok or Err. If Ok, the function executes as expected. If Err, the function is in error.

What does Result do?

According to the Rust Authoritative Guide

The Result type is a representation of a possible Result during a calculation. By convention, if one result is expected to be Ok, then the other result is unexpected, known as Err.

Please get straight again

The function returns the value. These values have specific data types. The function can return a Result of type Result. The Result type changes depending on whether the function executes as expected. A programmer can then write code that returns A if the function performs as expected and B if an exception is encountered.

If Result is not processed, an exception is raised

error[E0308]: mismatched types
  --> main.rs:20:26
   |
20 |     let my_number: f64 = my_string.trim().parse(); //.unwrap();
   |                          ^^^^^^^^^^^^^^^^^^^^^^^^ expected f64, found enum `std::result::Result`
   |
   = note: expected type `f64`
              found type `std::result::Result<_, _>`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0308`.
compiler exit status 1
Copy the code

The key part of the error message is “Expected F64, found enum.”

- "Expected U32, found enum" - "Expected String, found enum" - "Expected [insert type here], found enum"Copy the code

If you get an error like the one above, it is because you need to handle the Result data returned by the function

A program with Result of type Error

use std::io::{stdin, self, Write}; fn main(){ let mut my_string = String::new(); print! (" Enter a number: "); io::stdout().flush().unwrap(); Stdin ().read_line(&mut my_string). Expect (" Did not enter a correct string "); let my_number: f64 = my_string.trim().parse(); println! (" Yay! You entered a number. It was {:? } ", my_num); }Copy the code

In this program, it prompts the user for a number. The input is then read in as a string and stored. We want a numeric type, not a String, so we need to use the parse() function to convert it to a 64-bit floating-point number (f64).

If the user enters a number, the parse() function converts it to F64 with no problem. But we still get an error.

The error occurred because the parse() function doesn’t just convert a String to a number and return it. Instead, it takes a string, converts it to a number, and returns the Result type. The Result type needs to be unpacked to get the value we need.

Fix errors with Unwrap() or Expect()

Converted numbers can be “unpacked” from Result by appending a call to the unwrap() function to parse(), similar to this:

let my_number: f64 = my_string.trim().parse().unwrap();
Copy the code

The unwrap() function shows the type in Result, which can be Ok or Err. If the package type in Result is Ok, unwrap() returns its value. If the type in Result is Err, unwrap() crashes the program.

You can also use the expect() function to handle Result as follows:

Let my_number: f64 = my_string.trim().parse(). Expect (" parse failed ");Copy the code

Expect () works like unwrap(), and if Result is Err, Expect () crashes the program and displays its string contents — “Parse failed.” — in standard output.

Disadvantages of using unwrap() and Expect ()

When we use the unwrap() and expect() functions, the program crashes if we encounter an error. This may be tolerable if the probability of an error is very small, but in some cases the probability of an error is high.

In the example above, the user might have typed an error instead of a numeric value (perhaps a letter or a special symbol). We don’t want to crash every time the user types something wrong. Instead, we should prompt the user to enter a number. Result is useful in this scenario, especially when combined with a pattern matching expression.

Fix errors with matching expressions

use std::io::{stdin, self, Write}; fn main(){ let mut my_string = String::new(); print! (" Enter a number: "); io::stdout().flush().unwrap(); let my_num = loop { my_string.clear(); Stdin ().read_line(&mut my_string). Expect (" Did not enter a correct string "); match my_string.trim().parse::<f64>() { Ok(_s) => break _s, Err(_err) => println! (" Try again. Enter a number. ")}}; println! (" You rose {:? } ", my_num); }Copy the code

If you ask me how to implement, above is the sample code!

The difference between the inelegant and elegant implementations mentioned earlier is within the body of the loop. We can break it down.

The code analysis

Before loop, we prompt the user for a number. Next we declare my_num.

We assign the value returned from the body of the loop (the user’s input, which will convert from string to number) to my_num:

let my_num = loop {
Copy the code

In the body of the loop, we block waiting for user input. We then receive input from the user, and in this process we have three problems to solve.

  • 1. We need to make sure that the user enters numbers and not other characters, a word or a letter.
  • 2. The Rustread_line()The function can take user input as a string. We need to convert it to a floating point.
  • 3. If the user does not enter a number, we need to clean up the variable and prompt and wait for the user to enter again.

In part 3 the problem (cleaning up my_string variables) is already implemented in the first line of the loop:

my_string.clear();
Copy the code

Next, we accept input from the user:

Stdin ().read_line(&mut my_string). Expect (" Did not enter a correct string ");Copy the code

The read_line() function returns a Result type. We use the Expect () function to handle it. This is perfectly fine in this case, because read_line() has very little chance of failing. The user can usually enter only one string at the terminal, and this is what read_line() needs to handle.

Read_line () returns the string entered by the user and stores it in the my_string variable.

Important part

Now that we have stored the input string in my_string, we need to convert it to a floating point number. You can do this using the parse() function and then return the floating-point result. So we have more than the Result type to deal with, but this time, we’re likely to get an error. Parse () returns an error type of Result (Err) if the user enters a non-number. If that happens, we don’t want the program to crash. Instead, you want to prompt the user to try again because they did not enter the correct number. To do this, we need to write the logic when the call to parse() succeeds, and also when the call fails. Similar to processing the possible results of a matching expression one by one.

Parsing match expression

match my_string.trim().parse::<f64>() { Ok(_s) => break _s, Err(_err) => println! (" Try again. Enter a number ")}Copy the code

First, we use the match keyword to declare a match expression. We then provide possible values that match the expression. The value is as follows:

my_string.trim().parse::<f64>()
Copy the code

This code takes the my_string argument, which saves the user’s input and feeds it to the trim() function. The trim() function removes any extra blank lines or Spaces that may exist on either side of the string. We need trim() because the read_line() function appends an extra blank line to the input, which causes the conversion to fail. My_string, which has been cleaned of whitespace characters, is then passed to the parse() function, which tries to convert it to a floating point number.

If parse() successfully converts my_string to a number, it returns Ok. In this case, we can get floating-point numbers. If the user enters something other than a number, parse() will not complete the conversion properly and will return Err.

In the curly braces (body) of the matching expression, we tell the computer what to do based on the type returned by parse() :

Ok(_s) => break _s, Err(_err) => println! (" Try again. Enter a number. ")Copy the code

If the result is Ok, parse() can convert the type. At this point, we call a break to stop the loop and return the value stored in Ok, which is placed in the _S variable.

If the result is Err, parse() cannot complete the conversion. At this point, we tell the user “Try again. Enter a number “. Since we do not call break, the loop restarts.

If Result had to be explained in one sentence, it would be this: If a function returns Result, a matching expression can perform different code depending on whether the Result is Ok or Err.

Use Result in your function

Now that you know how to handle Result, you might want to use it in functions you create yourself.

Let’s start with an example.

fn main(){ let my_num = 50; Fn is_it_fifty(num: u32) -> Result<u32, &' static STR > {let error = "It didn't work"; if num == 50 { Ok(num) } else { Err(error) } } match is_it_fifty(my_num) { Ok(_v) => println! (" Good! My_num is 50 "), Err(_e) => println! (" Error. My_num is {:? }}} ", my_num)Copy the code

This program checks the value of my_num. If the value is 50, it indicates success. If not, it indicates an error.

The body of this code is the is_IT_fifty () function. It is a declarative function that returns a result. Let’s look at the code line by line.

First, we declare my_num and assign it a value. Then, we declare the is_it_fifty() function:

Fn is_it_fifty(num: u32) -> Result<u32, &' static STR > {Copy the code

In our declaration, we specify that the function takes a parameter named num of type 32 bit unsigned integer type (u32). Next, we specify the return value type of the function. The function returns a result of type u32 or string (&’static STR)

Next, we write the body of is_it_fifty().

Let error = "It didn't work"; if num == 50 { Ok(num) } else { Err(error) }Copy the code

The code in the function body is an if else expression. It is used to determine the parameters passed in.

If the value is 50, the function returns Result for Ok. Ok will contain the value (num) passed to the function.

If the argument is not 50, the function returns the Result of Err. Err contains the value of the error variable, which is “It didn’t work.”

Whenever you use this function, you must process the Result it returns. In our program, like most Rust programs, this is done through a matching expression. I described partial match expressions earlier.

The Result type can be handled using either unwrap() or expect(), as explained earlier.

conclusion

Result is the return type of a function that indicates whether the function was successfully executed.

Many of Rust’s built-in functions return the Result type, and if so, there is no way around it. If a function returns Result, it must be handled properly.

A common way to handle Result is to use the unwrap() and _expect() functions, along with matching expressions.

You can return Result from a self-defined function. This is a good way to deal with mistakes.

That’s all you need to know about the Result types of Rust, but for more information or where I gathered this information, see the list of resources below.

resources

  • doc.rust-lang.org/std/result/

  • Doc.rust-lang.org/1.2.0/book/…

    • To viewmatching on enumsPart of the
  • Doc.rust-lang.org/1.30.0/book…

  • Doc.rust-lang.org/rust-by-exa…

  • Blog.jonstodle.com/things-i-en…

  • Stevedonovan. Making. IO/rust – gentle…

  • Doc.rust-lang.org/book/ch03-0…

  • Doc.rust-lang.org/std/result/…