24 days from Node.js to Rust
preface
In tutorial 8, we mentioned options when we introduced HashMap. When we queried the data corresponding to the key value of the HashMap, we could not guarantee the existence of the corresponding data, so the return result must be “empty”. There is no such thing as undefined or null in Rust, and in order to safely represent “null”, Option is needed to solve this problem
“Empty” is like an expected error. In this case, either the Result is correct or the Result is “empty”, which means failure. In order to deal with the expected error processing, Result is created. Result and Option often occur together, are handled similarly, and can be converted to each other if necessary
The body of the
reading
- The Rust Book: ch 06 – Enums
- Rust by Example: Enums
- The Rust Reference: Enumerations
- Rust by Example: match
- Rust by Example: if let
- Rust docs: Result
- Rust docs: Option
Option
If you haven’t studied Enums thoroughly, read the articles listed above
Rust’s enums differs from many other languages in that the Option enums is defined as follows:
pub enum Option<T> {
/// No value
None./// Some value `T`
Some(T),
}
Copy the code
The value can be either Option::None for “empty” or Option::Some(T) for Some value. We’ve touched on generics before, and you can read more about them in the reading list for tutorial 10
Creating and using options in Rust is simple:
fn main() {
let some = returns_some();
println!("{:? }", some);
let none = returns_none();
println!("{:? }", none);
}
fn returns_some() - >Option<String> {
Some("my string".to_owned())
}
fn returns_none() - >Option<String> {
None
}
Copy the code
When returning None, we also need to specify T in Option
Note: We can substitute Some and None for Option::Some() and Option::None because they were pre-introduced by Rust, read STD :: Prelude for information.
Result
The value of the Result::Err variable usually follows some convention, but if you look at the implementation, there are actually no constraints
pub enum Result<T, E> {
Ok(T),
Err(E),
}
Copy the code
Is there anything special about the creation of Ok() and Err()
fn main() {
let value = returns_ok();
println!("{:? }", value);
let value = returns_err();
println!("{:? }", value);
}
fn returns_ok() - >Result<String, MyError> {
Ok("This turned out great!".to_owned())
}
fn returns_err() - >Result<String, MyError> {
Err(MyError("This failed horribly.".to_owned()))
}
#[derive(Debug)]
struct MyError(String);
Copy the code
Struct is used here to indicate that Err can contain any value, such as struct, String, HashMap, or whatever
.unwrap()
Option and Result look straightforward, but are they really?
The confusion comes from how to get the value. You’ve already encountered.unwrap() in this tutorial, and if you use.unwrap() for None or Err, you’ll get an error. With Rust, if you pay enough attention to warnings, you can reduce exceptions by 99% and achieve faster, more stable results
How to get a value
.unwrap()
Use.unwrap() only if you make sure Some() or Ok() is used as an example of the previous IP address:
let ip_address = std::net::Ipv4Addr::from_str("127.0.0.1").unwrap();
Copy the code
Unwrap () is based on the coder’s knowledge of the code and needs to be able to verify its value before using it. For example, we can use.contains_key() to verify that HashMaps contain the specified key, and if so we can safely use.unwrap()
.unwrap_or()
.unwrap_or() will provide a default value for failure. The default value needs to be of the same type as the T for Ok(T) and Some(T) :
fn main() {
let value = returns_ok();
println!("{:? }", value.unwrap());
let value = returns_err();
// This must be of type String
println!("{:? }", value.unwrap_or("It really failed.".to_owned()));
}
fn returns_ok() - >Result<String.String> {
Ok("Success!".to_owned())
}
fn returns_err() - >Result<String.String> {
Err("Fail!".to_owned())
}
Copy the code
.unwrap_or_else(|| {})
.unwrap_or_else() takes a function and will take its return value in the case of None or Err. This can be used if the default value is expensive to calculate and there is no value in calculating it in advance
As with.unwrap_or(), the return type of the function needs to be the same as T
let unwrap_or_else = returns_none()
.unwrap_or_else(|| format!("Default value from a function at time {:? }", Instant::now()));
println!(
"returns_none().unwrap_or_else(|| {{... }}) : {:? }",
unwrap_or_else
);
Copy the code
| |… Closure syntax in Rust, more on this later
unwrap_or_default()
Unwrap_or_default () follows the Default value of the specified type if it does not exist. Default is a trait like Debug or Display.
In TypeScript you might write code like this:
et my_string = maybe_undefined || "";
Copy the code
In Rust it looks like this:
let my_string = maybe_none.unwrap_or_default(); // Assuming `T` is `String`.
Copy the code
You can implement Default like this:
enum Kind {
A,
B,
C,
}
impl Default for Kind {
fn default() - >Self { Kind::A }
}
Copy the code
Match expression
You can use match expressions to get values:
let match_value = match returns_some() {
Some(val) => val,
None= >"My default value".to_owned(),
};
println!("match {{... }}, {:? }", match_value);
Copy the code
If let expression
You can apply a conditional judgment to the specified variant of an enumeration:
if let Some(val) = returns_some() { println! ("if let : {:? }", val); }Copy the code
If the Option returned by returns_some() is Some(), its internal value will be bound to the identifier val, which is a bit of a strange syntax, but useful
? The operator
What does the following code show? Operator tips:
use std::fs::read_to_string;
fn main() - >Result<(), std::io::Error> {
let html = render_markdown(".. /README.md")? ;println!("{}", html);
Ok(())}fn render_markdown(file: &str) - >Result<String, std::io::Error> {
letsource = read_to_string(file)? ;Ok(markdown::to_html(&source))
}
Copy the code
- First of all we use we use in number three
-> Result<(), std::io::Error>
willmain()
Change the return value type toResult
, pay attention to(a)
isunit type
“This is another way of saying empty.-> Result<(), ... >
Means return null or fail - Second, in line 10 we use
std::fs::read_to_string()
, it takes a path and returnsResult<String, std::io::Error>
That means it will returnString
Type of file content orstd::io::Error
Type error - And then line 10
?
Operator automaticallyunwrap
If the result is failure then?
The operator returns the result directly to the callermain()
function - We continue in line 4
?
Operator to do automaticunwrap
Because ofmain()
The function has no caller, so if there is a failure, the program terminates - Due to the
main()
Is the return type ofResult<(), ... >
So we need to write another one at the endOk(())
? With the try!
In some old articles, you’ll read about try! The contents of the macro, try! In fact, right? No matter who’s good or bad, it’s always good to know more about the pioneers of operators. Try! The implementation of the
Macros are not the focus of this tutorial, but take a look at try! Implementation of the source code, I believe it is not difficult to understand:
macro_rules! r#try{ ($expr:expr $(,)?) = > {match $expr {
$crate::result::Result: :Ok(val) => val,
$crate::result::Result: :Err(err) => {
return $crate::result::Result: :Err($crate::convert::From::from(err)); }}}; }Copy the code
try! The macro receives an expression and uses it in the match statement, returning its value if the expression is Ok, or returning it early and converting the return value to Result Error if it fails
Error handling
Finally, let’s look at the difference if the file path is retrieved from an environment variable:
use std::fs::read_to_string;
fn main() - >Result<(), std::io::Error> {
lethtml = render_markdown()? ;println!("{}", html);
Ok(())}fn render_markdown() - >Result<String, std::io::Error> {
let file = std::env::var("MARKDOWN")? ;letsource = read_to_string(file)? ;Ok(markdown::to_html(&source))
}
Copy the code
We added a line of code to get the file path from an environment variable named “MARKDOWN”. If this environment variable is not present, the program will report an error. Expression to short-circuit, and now we get a compilation error:? Could not convert the error to STD :: IO :: error…
Now that you know Option and Result, that’s not the most difficult part, but how do you handle different errors, which we’ll talk about in the next tutorial
conclusion
Options and results are ubiquitous in Rust, and you need to understand them correctly. Enums are also ubiquitous, and you often use Enums rather than specific strings, numbers, or Booleans
In the next article, we’ll introduce you to Err
More and more
- Rust tutorial (1) From NVM to Rust
- Rust tutorial (2) from NPM to Cargo for the front end
- Configure Visual Studio Code in Rust (3)
- Rust tutorial (4) Hello World
- Rust Tutorial for the front end (5) Borrowing & Ownership
- Part 1 of Rust Tutorial (6) String
- Rust Tutorial (7)
- Rust Tutorial (8)
- Rust Tutorial (9)
- Rust tutorial for the front end (10) from Mixins to Traits
- Rust Tutorial (11) Module for the front-end
- Part 2 of Rust Tutorial (12) String
- Rust Tutorial (13) Results & Options