16.7 Boxing errors

DisplayFrom을 우리 에러 타입을 위해 구현함으로, 우리는 std 라이브러리 에러 처리 도구들을 거의 대부분 사용할 수 있었다. 하지만, 우리가 놓치고 있는 것이 있었으니: 쉽게 우리의 에러 타입을 Box하는 능력.

std 라이브러리들은 From을 통해 Error trait을 구현하는 모든 타입은 trait 객체 Box<Error>로 자동 변환된다. 라이브러리 유저에게 이는 다음과 같은 편의성을 제공한다:

fn foo(...) -> Result<T, Box<Error>> { ... }

사용자가 사용할 수 있는 다양한 외부 라이러리들은 각기 그들 자신의 고유한 에러 타입을 제공한다. 적절한 Result<T, E> 타입을 정의하기 위하여, 사용자는 몇 가지 선택지가 있다:

  • 라이브러리의 에러 타입을 포장하는 새 포장된 에러 정의
  • 에러 타입을 String이나 다른 중간 형태를 선택해 타입 변환
  • 에러 타입들을 Box하여 Box<Error>로 타입 소멸

"박싱(Boxing)"은 보편적으로 선택되는 에러 타입이다. 단점은 기본 에러 타입을 오직 런타임에만 알 수 있고 정적으로 결정되지 않는다. 위에서 언급했듯, Error trait을 구현하는 것이 필요하다:

trait Error: Debug + Display {
    fn description(&self) -> &str;
    fn cause(&self) -> Option<&Error>;
}

이 구현을 통해 가장 최근의 예제를 살펴보자. DoubleError였을 때와 마찬가지로 Box<Error>일 때도 유효하다:

use std::error;
use std::fmt;
use std::num::ParseIntError;

// 별칭을 `Box`로 변경.
type Result<T> = std::result::Result<T, Box<error::Error>>;

#[derive(Debug)]
enum DoubleError {
    EmptyVec,
    Parse(ParseIntError),
}

impl From<ParseIntError> for DoubleError {
    fn from(err: ParseIntError) -> DoubleError {
        DoubleError::Parse(err)
    }
}

impl fmt::Display for DoubleError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            DoubleError::EmptyVec =>
                write!(f, "please use a vector with at least one element"),
            DoubleError::Parse(ref e) => e.fmt(f),
        }
    }
}

impl error::Error for DoubleError {
    fn description(&self) -> &str {
        match *self {
            // 에러에 대한 짧은 설명. `Display`와 같을 필요 없다.
            DoubleError::EmptyVec => "empty vectors not allowed",
            // 이것은 이미 `Error`를 구현하므로, 그의 구현에 위임한다.
            DoubleError::Parse(ref e) => e.description(),
        }
    }

    fn cause(&self) -> Option<&error::Error> {
        match *self {
            // 아래 줄이 발생하지 않으니 `None`을 반환한다.
            DoubleError::EmptyVec => None,
            // 아래 구현된 에러 타입을 유발되면 암시적으로 `&error:Error`로 변환된다.
            // 이는 아래 타입이 이미 `Error` trait을 구현하기 때문에 동작한다.
            DoubleError::Parse(ref e) => Some(e),
        }
    }
}

fn double_first(vec: Vec<&str>) -> Result<i32> {
    let first = try!(vec.first().ok_or(DoubleError::EmptyVec));
    let parsed = try!(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!["93", "18"];
    let empty = vec![];
    let strings = vec!["tofu", "93", "18"];

    print(double_first(numbers));
    print(double_first(empty));
    print(double_first(strings));
}

See also:

Dynamic dispatch and Error trait

results matching ""

    No results matching ""