Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

에러 타입 정의하기

때로는 모든 서로 다른 에러들을 하나의 에러 타입으로 마스킹하는 것이 코드를 단순화합니다. 이를 커스텀 에러를 통해 보여주겠습니다.

Rust에서는 우리만의 에러 타입을 정의할 수 있습니다. 일반적으로 “좋은” 에러 타입은 다음과 같습니다:

  • 서로 다른 에러들을 동일한 타입으로 표현합니다
  • 사용자에게 보기 좋은 에러 메시지를 제공합니다
  • 다른 타입들과 비교하기 쉽습니다
    • 좋은 예: Err(EmptyVec)
    • 나쁜 예: Err("Please use a vector with at least one element".to_owned())
  • 에러에 대한 정보를 담을 수 있습니다
    • 좋은 예: Err(BadChar(c, position))
    • 나쁜 예: Err("+ cannot be used here".to_owned())
  • 다른 에러들과 잘 구성(compose)됩니다
use std::fmt;

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

// 우리의 에러 타입들을 정의합니다. 이들은 우리의 에러 처리 케이스에 맞게 커스터마이징될 수 있습니다.
// 이제 우리만의 에러를 작성하거나, 기저 에러 구현에 위임하거나, 혹은 그 사이의 작업을 수행할 수 있습니다.
#[derive(Debug, Clone)]
struct DoubleError;

// 에러의 생성은 표시 방식과 완전히 분리되어 있습니다.
// 표시 스타일 때문에 복잡한 로직이 어지러워질까 봐 걱정할 필요가 없습니다.
//
// 참고로 우리는 에러에 대한 추가 정보를 저장하지 않습니다. 즉, 정보를 담도록 타입을 수정하지 않고서는
// 어떤 문자열이 파싱에 실패했는지 알 수 없습니다.
impl fmt::Display for DoubleError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "두 배로 만들 첫 번째 항목이 유효하지 않습니다")
    }
}

fn double_first(vec: Vec<&str>) -> Result<i32> {
    vec.first()
        // 에러를 우리의 새로운 타입으로 변경합니다.
        .ok_or(DoubleError)
        .and_then(|s| {
            s.parse::<i32>()
                // 여기서도 새로운 에러 타입으로 업데이트합니다.
                .map_err(|_| DoubleError)
                .map(|i| 2 * i)
        })
}

fn print(result: Result<i32>) {
    match result {
        Ok(n) => println!("두 배가 된 첫 번째 값은 {}입니다", n),
        Err(e) => println!("에러: {}", e),
    }
}

fn main() {
    let numbers = vec!["42", "93", "18"];
    let empty = vec![];
    let strings = vec!["두부", "93", "18"];

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