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