?, 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. With dyn, 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.