16.5 Defining an error type
Rust는 우리 고유의 에러 타입을 정의할 수 있게 허용한다 일반적으로 "좋은" 에러 타입은:
- 서로 다른 에러를 같은 타입으로 표현
- 사용자에게 좋은 에러 메시지를 출력
- 다른 타입들과의 쉬운 비교
- Good:
Err(EmptyVec)
- Bad:
Err("Please use a vector with at least one element".to_owned())
- Good:
- 에러에 대한 정보를 보관
- Good:
Err(BadChar(c, position))
- Bad:
Err("+ cannot be used here".to_owned())
- Good:
주목할 점은 String
(아직까지 사용되고 있는)이 첫 두 항목을 만족하지만, 마지막 둘은 아니라는 점이다. 이는 String
에러가 장황하고 반응하기 어렵게 만든다. 이는 필요 없이 String
을 보기 좋게 형식화하는 것과 관련된 무거운 코드가 로직을 오염시킨다.
use std::num::ParseIntError; use std::fmt; type Result<T> = std::result::Result<T, DoubleError>; #[derive(Debug)] // 오류 타입들을 정의한다. 우리의 에러 처리를 위해 사용자 정의될 수 있다. // 이제 우리 자신의 오류를 작성하거나, 기본 오류 구현을 연기하거나, // 중간에 뭔가 할 수 있다. enum DoubleError { // 이 오류를 자세히 설명하기 위해 추가 정보가 필요하지 않다. EmptyVec, // 우리는 분석 오류에 대한 구현을 연기할 것이다. // 추가 정보를 제공하려면 타입에 데이터를 더 추가해야 한다. Parse(ParseIntError), } // 에러의 생성은 표시하는 방법과는 완전히 별개이다. // 표시하는 스타일을 위한 복잡한 로직으로 혼란스럽게 되는 것을 걱정할 필요가 없다. // // 에러에 대한 추가 정보는 저장하지 않는다. 이는 정보를 전달하기 위해 // 우리의 타입을 수정하지 않고 분석에 실패한 문자열을 말할 수 없음을 뜻한다. 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"), // 이는 래퍼로 `fmt`구현의 타입에 종속된다. DoubleError::Parse(ref e) => e.fmt(f), } } } fn double_first(vec: Vec<&str>) -> Result<i32> { vec.first() // 에러를 우리의 새 타입으로 변경한다. .ok_or(DoubleError::EmptyVec) .and_then(|s| s.parse::<i32>() // 여기도 새 에러 타입으로 업데이트. .map_err(DoubleError::Parse) .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!["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:
Result
and io::Result