?, dyn, and Macros
There are a few last things to highlight. We want to look at these because they are frequently used in the standard library and external crates.
?
?
is a convenient operator that you might frequently use. It is a shortcut for handling Result
and
Option
.
Recall from the Safety Features of Rust that you typically
use a Result
to handle successful return values and errors together. A Result
wraps a return
value or an error inside Ok()
or Err()
and you can use match
to get the return value or the
error.
fn function_with_result(success_or_fail: bool) -> Result<String, String> { match success_or_fail { true => Ok(String::from("success")), false => Err(String::from("fail")), } } fn function_that_handles_result() -> Result<(), String> { let result = function_with_result(true); match result { Ok(success_result) => { println!("success_result: {}", success_result); Ok(()) }, Err(error_result) => Err(error_result) } } fn main() { function_that_handles_result(); }
This works well but it can be repetitive and tiring since you typically make many function calls and
need to handle a Result
. Thus, Rust provides ?
, an operator to handle a Result
easily. What it
does is that, if the Result
is Ok()
, it pulls out what's inside Ok()
, and if the Result
is
Err()
, it returns Err()
and exits from the entire function. Using ?
, the above code can be
revised as follows.
fn function_with_result(success_or_fail: bool) -> Result<String, String> { match success_or_fail { true => Ok(String::from("success")), false => Err(String::from("fail")), } } fn function_that_handles_result() -> Result<(), String> { let success_result = function_with_result(true)?; println!("success_result: {}", success_result); Ok(()) } fn main() { function_that_handles_result(); }
?
can also be used to handle Option
. If the value is None
, it exits the function early and
returns None
. If the value is Some()
, it pulls the value out of Some()
.
dyn
dyn
is a keyword that represents a trait
object. A trait object is an object of
any type that implements the trait. You can see the use of it in error propagation like the
following example from the Rust
book.
fn main() -> Result<(), Box<dyn std::error::Error>> { let f = std::fs::File::open("hello.txt")?; Ok(()) }
Box<dyn Error>>
means that it is a Box
that contains an object of any type that implements the
Error
trait.
You may recall that in More on Struct and Traits, we talked about
generic type parameters with trait bounds. The purpose is almost exactly the same, i.e., it
represents any type that implements certain traits. dyn
is different in two ways.
dyn
can only take one trait. (There are some nuances here and look at the Rust Reference on traits for that.)dyn
is dynamic while a generic type parameter with a trait bound is static. This means that for a generic type with a trait bound, the compiler will automatically generate code for each possible type. Withdyn
, the compiler will inject code that finds the right type (which is called dynamic dispatch).
Macros
Similar to C/C++, Rust provides macros. The first example you typically see is println!()
. !
indicates that it is a macro. A good resource to learn about macros is (again) the Rust book. It has
a section on macros.