Rewrite Result

This commit is contained in:
Dhghomon 2020-08-26 23:24:50 +09:00 committed by GitHub
parent c2ebfbf145
commit 8310de02fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

110
README.md
View File

@ -46,6 +46,7 @@ It is now late August, and *Easy Rust* is over 400 pages long. Easy Rust is almo
- [References and the dot operator](#references-and-the-dot-operator) - [References and the dot operator](#references-and-the-dot-operator)
- [Generics](#generics) - [Generics](#generics)
- [Option and Result](#option-and-result) - [Option and Result](#option-and-result)
- [Option](#option)
- [Result](#result) - [Result](#result)
- [Other collections](#other-collections) - [Other collections](#other-collections)
- [HashMap (and BTreeMap)](#hashmap-and-btreemap) - [HashMap (and BTreeMap)](#hashmap-and-btreemap)
@ -3460,6 +3461,8 @@ We understand enums and generics now, so we can understand `Option` and `Result`
We will start with `Option`. We will start with `Option`.
### Option
You use `Option` when you have a value that might exist, or might not exist. When a value exists it is `Some(value)` and when it doesn't it's just `None`, Here is an example of bad code that can be improved with `Option`. You use `Option` when you have a value that might exist, or might not exist. When a value exists it is `Some(value)` and when it doesn't it's just `None`, Here is an example of bad code that can be improved with `Option`.
```rust ```rust
@ -3632,6 +3635,8 @@ Result is similar to Option, but here is the difference:
- Option is about `Some` or `None` (value or no value), - Option is about `Some` or `None` (value or no value),
- Result is about `Ok` or `Err` (okay result, or error result). - Result is about `Ok` or `Err` (okay result, or error result).
So `Option` is if you are thinking: "Maybe there will be something, and maybe there won't." But `Result` is if you are thinking: "Maybe it will fail."
To compare, here are the signatures for Option and Result. To compare, here are the signatures for Option and Result.
```rust ```rust
@ -3648,25 +3653,70 @@ enum Result<T, E> {
fn main() { } fn main() { }
``` ```
So Result has a value inside of `Ok`, and a value inside of `Err`. That is because errors usually have information inside them. So Result has a value inside of `Ok`, and a value inside of `Err`. That is because errors usually (and should have) have information inside them.
`Result<T, E>` means you need to think of what you want to return for `Ok`, and what you want to return for `Err`. Actually, you can decide anything. Even this is okay: `Result<T, E>` means you need to think of what you want to return for `Ok`, and what you want to return for `Err`. Actually, you can decide anything. Even this is okay:
```rust ```rust
fn main() {
check_error();
}
fn check_error() -> Result<(), ()> { fn check_error() -> Result<(), ()> {
Ok(()) Ok(())
} }
fn main() {
check_error();
}
``` ```
`check_error` says "return `()` if we get `Ok`, and return `()` if we get `Err`". Then we return `Ok` with a `()`. `check_error` says "return `()` if we get `Ok`, and return `()` if we get `Err`". Then we return `Ok` with a `()`.
Sometimes a function with Result will use a `String` for the `Err` value. This is not the best method to use, but sometimes it is okay. The compiler gives us an interesting warning:
```text
warning: unused `std::result::Result` that must be used
--> src\main.rs:6:5
|
6 | check_error();
| ^^^^^^^^^^^^^^
|
= note: `#[warn(unused_must_use)]` on by default
= note: this `Result` may be an `Err` variant, which should be handled
```
This is true: we only returned the `Result` but it could have been an `Err`. So let's handle the error a bit, even though we're still not really doing anything.
```rust ```rust
fn give_result(input: i32) -> Result<(), ()> {
if input % 2 == 0 {
return Ok(())
} else {
return Err(())
}
}
fn main() {
if give_result(5).is_ok() {
println!("It's okay, guys")
} else {
println!("It's an error, guys")
}
}
```
This prints `It's an error, guys`. So we just handled our first error.
Remember, the four methods to easily check are `.is_some()`, `is_none()`, `is_ok()`, and `is_err()`.
Sometimes a function with Result will use a `String` for the `Err` value. This is not the best method to use, but it is a little better than what we've done so far.
```rust
fn check_if_five(number: i32) -> Result<i32, String> {
match number {
5 => Ok(number),
_ => Err("Sorry, the number wasn't five.".to_string()), // This is our error message
}
}
fn main() { fn main() {
let mut result_vec = Vec::new(); // Create a new vec for the results let mut result_vec = Vec::new(); // Create a new vec for the results
@ -3676,13 +3726,6 @@ fn main() {
println!("{:?}", result_vec); println!("{:?}", result_vec);
} }
fn check_if_five(number: i32) -> Result<i32, String> {
match number {
5 => Ok(number),
_ => Err("Sorry, the number wasn't five.".to_string()), // This is our error message
}
}
``` ```
Our vec prints: Our vec prints:
@ -3695,6 +3738,7 @@ Err("Sorry, the number wasn\'t five.")]
Just like Option, `.unwrap()` on `Err` will panic. Just like Option, `.unwrap()` on `Err` will panic.
```rust ```rust
// ⚠️
fn main() { fn main() {
let error_value: Result<i32, &str> = Err("There was an error"); // Create a Result that is already an Err let error_value: Result<i32, &str> = Err("There was an error"); // Create a Result that is already an Err
println!("{}", error_value.unwrap()); // Unwrap it println!("{}", error_value.unwrap()); // Unwrap it
@ -3709,16 +3753,16 @@ thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: "There w
This information helps you fix your code. `src\main.rs:30:20` means "inside main.rs in directory src, on line 30 and column 20". So you can go there to look at your code and fix the problem. This information helps you fix your code. `src\main.rs:30:20` means "inside main.rs in directory src, on line 30 and column 20". So you can go there to look at your code and fix the problem.
You can also create your own error types. Result functions in the standard library usually have their own error types. For example: You can also create your own error types. Result functions in the standard library and other people's code usually do this. For example, this function from the standard library:
```rust ```rust
// 🚧 // 🚧
pub fn from_utf8(vec: Vec<u8>) -> Result<String, FromUtf8Error> pub fn from_utf8(vec: Vec<u8>) -> Result<String, FromUtf8Error>
``` ```
This function take a vector of bytes (`u8`) and tries to make a `String`. So the success case for the Result is a `String` and the error case is `FromUtf8Error`. You can give your error type any name you want. We will create our own error types later, because first we need to learn other things. This function take a vector of bytes (`u8`) and tries to make a `String`. So the success case for the Result is a `String` and the error case is `FromUtf8Error`. You can give your error type any name you want.
Using a `match` with Option and Result sometimes requires a lot of code. For example, the `.get()` method returns an Option on a Vec. Using a `match` with `Option` and `Result` sometimes requires a lot of code. For example, the `.get()` method returns an `Option` on a `Vec`.
```rust ```rust
fn main() { fn main() {
@ -3752,7 +3796,7 @@ fn main() {
} }
``` ```
This is good, but we don't do anything for `None`. Here we can make the code smaller by using `if let`. `if let` means "do something if it matches, and don't do anything if it doesn't". `if let` is when you don't care about matching for everything. This is good, but we don't do anything for `None` because we don't care. Here we can make the code smaller by using `if let`. `if let` means "do something if it matches, and don't do anything if it doesn't". `if let` is when you don't care about matching for everything.
```rust ```rust
fn main() { fn main() {
@ -3766,7 +3810,9 @@ fn main() {
} }
``` ```
`if let Some(number) = my_vec.get(index)` means "if you get `Some(number)` from `my_vec.get(index)`". Also note: it uses one `=`. It is not a boolean. **Important to remember**: `if let Some(number) = my_vec.get(index)` means "if you get `Some(number)` from `my_vec.get(index)`".
Also note: it uses one `=`. It is not a boolean.
`while let` is like a while loop for `if let`. Imagine that we have weather station data like this: `while let` is like a while loop for `if let`. Imagine that we have weather station data like this:
@ -3775,24 +3821,44 @@ fn main() {
["Athens", "sunny", "not humid", "20", "10", "50"] ["Athens", "sunny", "not humid", "20", "10", "50"]
``` ```
We want to get the numbers, but not the words. For the numbers, we can use a method called `parse::<i32>()`. `parse()` is the method, and `::<i32>` is the type. It will try to turn the `&str` into an `i32`, and give it to us if it can. We want to get the numbers, but not the words. For the numbers, we can use a method called `parse::<i32>()`. `parse()` is the method, and `::<i32>` is the type. It will try to turn the `&str` into an `i32`, and give it to us if it can. It returns a `Result`, because it might not work (like if you wanted it to parse "Billybrobby" - that's not a number).
We will also use `.pop()`. This takes the last item off of the vector. We will also use `.pop()`. This takes the last item off of the vector.
```rust ```rust
fn main() { fn main() {
let mut weather_vec = vec!["Berlin", "cloudy", "5", "-7", "78"]; let weather_vec = vec![
while let Some(information) = weather_vec.pop() { // This means: keep going until you can't pop anymore vec!["Berlin", "cloudy", "5", "-7", "78"],
vec!["Athens", "sunny", "not humid", "20", "10", "50"],
];
for mut city in weather_vec {
println!("For the city of {}:", city[0]); // In our data, every first item is the city name
while let Some(information) = city.pop() {
// This means: keep going until you can't pop anymore
// When the vector reaches 0 items, it will return None // When the vector reaches 0 items, it will return None
// and it will stop. // and it will stop.
if let Ok(number) = information.parse::<i32>() { // Try to parse the variable we called information if let Ok(number) = information.parse::<i32>() {
// Try to parse the variable we called information
// This returns a result. If it's Ok(number), it will print it // This returns a result. If it's Ok(number), it will print it
println!("The number is: {}", number); println!("The number is: {}", number);
} // We don't write anything here because we do nothing if we get an error. Throw them all away
} }
} }
} }
``` ```
This will print:
```text
For the city of Berlin:
The number is: 78
The number is: -7
The number is: 5
For the city of Athens:
The number is: 50
The number is: 10
The number is: 20
```
## Other collections ## Other collections