Выбрать главу

The stdlib helps in boxing our errors by having Box implement conversion from any type that implements the Error trait into the trait object Box<Error>, via From.

use std::error;

use std::fmt;

// Change the alias to `Box<error::Error>`.

type Result<T> = std::result::Result<T, Box<dyn error::Error>>;

#[derive(Debug, Clone)]

struct EmptyVec;

impl fmt::Display for EmptyVec {

fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {

write!(f, "invalid first item to double")

}

}

impl error::Error for EmptyVec {}

fn double_first(vec: Vec<&str>) -> Result<i32> {

vec.first()

.ok_or_else(|| EmptyVec.into()) // Converts to Box

.and_then(|s| {

s.parse::<i32>()

.map_err(|e| e.into()) // Converts to Box

.map(|i| 2 * i)

})

}

fn print(result: Result<i32>) {

match result {

Ok(n) => println!("The first doubled is {}", n),

Err(e) => println!("Error: {}", e),

}

}

fn main() {

let numbers = vec!["42", "93", "18"];

let empty = vec![];

let strings = vec!["tofu", "93", "18"];

print(double_first(numbers));

print(double_first(empty));

print(double_first(strings));

}

הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Dynamic dispatch and Error trait

Notice in the previous example that our immediate reaction to calling parse is to map the error from a library error into a boxed error:

.and_then(|s| s.parse::<i32>()

.map_err(|e| e.into())

Since this is a simple and common operation, it would be convenient if it could be elided. Alas, because and_then is not sufficiently flexible, it cannot. However, we can instead use ?.

? was previously explained as either unwrap or return Err(err). This is only mostly true. It actually means unwrap or return Err(From::from(err)). Since From::from is a conversion utility between different types, this means that if you ? where the error is convertible to the return type, it will convert automatically.

Here, we rewrite the previous example using ?. As a result, the map_err will go away when From::from is implemented for our error type:

use std::error;

use std::fmt;

// Change the alias to `Box<dyn error::Error>`.

type Result<T> = std::result::Result<T, Box<dyn error::Error>>;

#[derive(Debug)]

struct EmptyVec;

impl fmt::Display for EmptyVec {

fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {

write!(f, "invalid first item to double")

}

}

impl error::Error for EmptyVec {}

// The same structure as before but rather than chain all `Results`

// and `Options` along, we `?` to get the inner value out immediately.

fn double_first(vec: Vec<&str>) -> Result<i32> {

let first = vec.first().ok_or(EmptyVec)?;

let parsed = first.parse::<i32>()?;

Ok(2 * parsed)

}

fn print(result: Result<i32>) {

match result {

Ok(n) => println!("The first doubled is {}", n),

Err(e) => println!("Error: {}", e),

}

}

fn main() {

let numbers = vec!["42", "93", "18"];

let empty = vec![];

let strings = vec!["tofu", "93", "18"];

print(double_first(numbers));

print(double_first(empty));

print(double_first(strings));

}

הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

This is actually fairly clean now. Compared with the original panic, it is very similar to replacing the unwrap calls with ? except that the return types are Result. As a result, they must be destructured at the top level.

From::from and ?

An alternative to boxing errors is to wrap them in your own error type.

use std::error;

use std::error::Error as _;

use std::num::ParseIntError;

use std::fmt;

type Result<T> = std::result::Result<T, DoubleError>;

#[derive(Debug)]

enum DoubleError {

EmptyVec,

// We will defer to the parse error implementation for their error.