16.6 Other uses of try!
앞의 예제에서 parse
호출 시에 우리는 즉시 라이브러리가 제공하는 에러를 우리의 사용자 정의 에러 타입으로 매핑했다:
.and_then(|s| s.parse::<i32>()
.map_err(DoubleError::Parse)
이게 간단하고 보편적 작업이기에, 생략할 수 있다면 편리 할 것이다. 하지만 and_then
이 충분히 유연하지 않기 때문에, 할 수가 없다. 그래서 우리는 try!
를 대신 사용할 수 있다.
try!
는 앞서 설명했 듯 unwrap
이나 return Err(err)
중 하나다. 이는 거의 대부분의 경우에는 true이다. 이게 실제로 뜻하는 바는 unwrap
이나 return Err(From::from(err))
라는 것이다. From::from
은 다른 타입 간에 변환 도구이기에, 이는 당신이 에러를 반환 타입으로 변환 가능할 때 try!
하면 이는 자동으로 변환될 것이라는 뜻이다.
여기 try!
로 재작성된 이전 예제가 있다. 그 결과 map_err
는 사라지고 From::from
이 우리의 에러 타입으로 구현되어 map_err는 사라지게 된다:
use std::num::ParseIntError; use std::fmt; type Result<T> = std::result::Result<T, DoubleError>; #[derive(Debug)] enum DoubleError { EmptyVec, Parse(ParseIntError), } // `ParseIntError`를 `DoubleError`로 변환하는 구현. // `ParseIntError`가 `DoubleError`로 변환이 필요하면 // 자동으로 `try!`에 의해 호출된다. 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), } } } // 이전과 같은 구조이지만 `Results`와 `Options`를 모두 연결하는 대신 // 내부 값을 즉시 얻기 위해 `try!` 한다. fn double_first(vec: Vec<&str>) -> Result<i32> { // `None`을 변환하는 방식을 정함을 통해 항상 `Result`를 반환한다. 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)); }
이제는 꽤나 명료해졌다. 원본 panic
과 비교했을 때, 이와 매우 유사한 것은 unwrap
호출을 try!
로 변경하여 Result
을 반환하도록 하는 것이다. 결과적으로 이들은 최상위 레벨에서는 반드시 역구조화 되어야 한다
이런 종류의 에러 처리가 항상 unwrap
을 대체하기를 기대해서는 안된다. 이런 타입의 에러 처리는 우리의 라인 수를 세 배로 늘렸고 정말 간결하게 될 수가 없다(심지어 적은 코드 사이즈에 큰 편견이 있더라도).
실제로 1000줄의 라이브러리를 unwrap
에서 더 적절한 에러 처리로 옮기는 것은 100라인의 코드를 추가하는 것으로 가능할지도 모른다. 하지만 필요한 리팩토링은 대부분 명확히 사소한 작업이 아니다.
많은 라이브러리들이 Display
만 구현하고 필요에 따라 From
을 추가한다. 하지만 더 중요한 라이브러리는 에러 처리 구현에 대한 높은 기대치를 만족시켜야 한다.
See also:
From::from
and try!