1.1.0으로 업데이트

sarojaba authored
revision f3c78f12e6e8b6fe068375e4558d98d5c8577c05
error-handling
# 4.7. 오류 처리 (Error Handling) - 909%

> The best-laid plans of mice and men
> Often go awry
>
> "Tae a Moose", Robert Burns

때때로, 일은 잘못 굴러갑니다. 불가피한 것들이 일어날때를 위한 계획을
가지는 것은 중요합니다. Rust는 당신의 프로그램에서 일어날 지도 모르는
(우리 솔직해지죠. 반드시 일어납니다.) 에러들을 처리하기 위한 풍부한 지원 방법들을
가지고 있습니다.

당신의 프로그램에서 일어날 수 있는 주된 에러들은 두가지 종류가 있습니다: 실패(failure)와 패닉(panic)입니다. 두가지의 차이점에 대해 말해보고, 각각을 어떻게 처리할지 논의해 봅시다. 그런다음, 실패를 패닉으로 업그레이드 하는 것에 대해 논의할 것입니다.

# 실패 vs. 패닉

Rust는 두가지 유형의 에러를 구분하기 위해 두가지 용어를 씁니다: 실패(Failure),
그리고 패닉(Panic) 입니다. *실패*는 어떤 방법으로 복구될 수 있는 에러를 뜻합니다. *패닉*은 복구될 수 없는 에러를 뜻합니다.

'복구(recover)'의 뜻은 무엇일까요? 흠, 대부분의 경우에 에러의 가능성이 있습니다.
예를 들어, parse 함수를 고려해 봅시다.

```ignore
"5".parse();
```

이 메쏘드는 문자열을 다른 타입으로 변환합니다. 그러나 그것이 문자열이기 때문에,
당신은 변환이 실제로 일어날지를 확신할 수 없습니다. 예를 들어, 이것은 무엇으로
변환됩니까?

```ignore
"hello5world".parse();
```

이것은 동작하지 않습니다. 따라서 우리가 아는 것은, 이 함수가 단지 몇몇 입력에 대해서만
적절하게 작동한다는 겁니다. 이것은 예상된 동작입니다. 우리는 이러한 종류의 에러를 *실패*라고
합니다.

반면에, 때때로, 예상되지 않거나, 복구 불가능한 에러가 있습니다. 이에 대한
전통적인 예제는 `assert!`가 있습니다.

```rust
# let x = 5;
assert!(x == 5);
```

우리는 `assert!`를 무언가가 참이라고 선언하기 위해 사용합니다. 만약 그것이
참이 아니라면, 무언가가 대단히 잘못된 것입니다. 현재 상태에서 무언가를
계속할 수 없을 정도로 충분히 잘못된 겁니다. 다른 예제는 `unreachable!()`
매크로를 사용한 것입니다.

```{rust,ignore}
enum Event {
NewRelease,
}

fn probability(_: &Event) -> f64 {
// real implementation would be more complex, of course
0.95
}

fn descriptive_probability(event: Event) -> &'static str {
match probability(&event) {
1.00 => "certain",
0.00 => "impossible",
0.00 ... 0.25 => "very unlikely",
0.25 ... 0.50 => "unlikely",
0.50 ... 0.75 => "likely",
0.75 ... 1.00 => "very likely",
}
}

fn main() {
std::io::println(descriptive_probability(NewRelease));
}
```

이것은 다음과 같은 에러를 발생시킵니다.

```text
error: non-exhaustive patterns: `_` not covered [E0004]
```
우리는 위의 코드가 가능한 모든 경우를 다 다루고 있음을 알고 있지만, Rust는
그렇지 못합니다. Rust는 확률이 항상 0.0과 1.0 사이인 것을 알지 못합니다. 따라서
우리는 다른 경우에 대한 것을 추가해 주어야 합니다:

```rust
use Event::NewRelease;

enum Event {
NewRelease,
}

fn probability(_: &Event) -> f64 {
// real implementation would be more complex, of course
0.95
}

fn descriptive_probability(event: Event) -> &'static str {
match probability(&event) {
1.00 => "certain",
0.00 => "impossible",
0.00 ... 0.25 => "very unlikely",
0.25 ... 0.50 => "unlikely",
0.50 ... 0.75 => "likely",
0.75 ... 1.00 => "very likely",
_ => unreachable!()
}
}

fn main() {
println!("{}", descriptive_probability(NewRelease));
}
```

절대 `_`인 경우는 있을 수 없을 것이기에, 우리는 이것을 표시하기 위해
`unreachable!()` 매크로를 사용합니다. `unreachable!()`는 `Result`가 아닌 어떤
다른 종류의 에러를 줍니다. Rust는 이런 부류의 에러를 패닉(Panic)이라고 부릅니다.

# `Option`과 `Result`와 함께 에러 처리하기

어떤 함수가 실패할 지도 모른다는 것을 표시하는 가장 간단한 방법은 `Option`
타입을 사용하는 것입니다. 예를 들어, `find` 메쏘드는 문자열에서 패턴을 찾는 것을
시도하며, `Option`을 반환합니다:


```rust
let s = "foo";

assert_eq!(s.find('f'), Some(0));
assert_eq!(s.find('z'), None);
```

이것은 가장 단순한 경우에는 적합하지만, 실패한 경우에는 우리에게 충분한 정보를
제공하지 않습니다. 만약 우리가 왜 실패했는지를 알고 싶다면? 이것을 위해서는
우리는 `Result` 타입을 사용해야 합니다. 이 타입의 모습은 다음과 같습니다.

```rust
enum Result {
Ok(T),
Err(E)
}
```

이 열거형은 러스트 그 자체에 의해 제공되며, 이걸 사용하기 위해 따로 정의할 필요는
없습니다. `Ok(T)` 변량(variant)은 성공을 나타내며, `Err(E)` 변량은 실패(failure)를 나타냅니다. `Option` 대신 `Result`를 리턴하는 것은 몇몇 사소한 상황을 제외한 거의 모든 경우에
권장됩니다.

여기에 `Result의 사용 예가 있습니다:

```rust
#[derive(Debug)]
enum Version { Version1, Version2 }

#[derive(Debug)]
enum ParseError { InvalidHeaderLength, InvalidVersion }

fn parse_version(header: &[u8]) -> Result {
if header.len() < 1 {
return Err(ParseError::InvalidHeaderLength);
}
match header[0] {
1 => Ok(Version::Version1),
2 => Ok(Version::Version2),
_ => Err(ParseError::InvalidVersion)
}
}

let version = parse_version(&[1, 2, 3, 4]);
match version {
Ok(v) => {
println!("working with version: {:?}", v);
}
Err(e) => {
println!("error parsing header: {:?}", e);
}
}
```

이 함수는 일어날 수 있는 다양한 에러들을 열거하기 위해
열거형 `ParseError`을 사용하고 있습니다.

[`Debug`](../std/fmt/trait.Debug.html) 트레이트는 `{:?}` 형식 연산을 이용해 열거값을 출력합니다.

# `panic!`을 통한 회복 불가능한 에러

예상할 수 없고 복구할 수 없는 에러에 관하여, `panic!` 매크로는 패닉을 유도합니다.
이것은 현재 쓰레드를 붕괴(crash)시키고, 에러를 줍니다.

```{rust,ignore}
panic!("boom");
```

위의 코드를 실행시키면

```text
thread '
' panicked at 'boom', hello.rs:2
```

위와 같은 에러가 발생합니다.

이런 경우는 상대적으로 드물기 때문에, 패닉은 삼가서 사용하세요.

# 실패(failure)를 패닉(panic)으로 업그레이드하기

특정 상황에서, 비록 함수가 단지 실패(Failure)를 일으킨다 하더라도,
우리는 이것을 패닉으로 간주하여 처리하고 싶을지도 모릅니다. 예를 들어,
`io::stdin().read_line(&mut buffer)`는 줄을 읽을때 에러가 발생하면 `Result`를
리턴합니다. 이것은 에러로부터 복귀하거나 에러를 처리하는 것을 가능하게 합니다.

그러나 이 에러를 처리하기를 원치 않고, 그냥 프로그램을 정지시키는 쪽을 원한다면,
우리는 `unwrap()` 메쏘드를 사용할 수 있습니다.

```{rust,ignore}
io::stdin().read_line(&mut buffer).unwrap();
```

`Result`가 `Err`인 경우에, `unwrap()`은 `panic!`을 일으킵니다. 이것은 기본적으로
`값을 줘, 그리고 뭔가 잘못 돌아간다면, 나는 가차없이 판을 뒤집을거야(crash)`라고
말하는 듯 합니다. 이런 방식은 에러를 매칭해서 복구를 시도하는 것보다 덜 유연한
방식이지만, 대신 현저하게 짧습니다. 때때로, 그냥 붕괴(crash)시키는 것이 적절하기도
합니다.

`unwrap()`보다 조금 더 멋진 방법도 있는데 다음과 같습니다:

```{rust,ignore}
let mut buffer = String::new();
let input = io::stdin().read_line(&mut buffer)
.ok()
.expect("Failed to read line");
```

`ok()`는 `Result`를 `Option`으로 바꾸며, `expect()`는 `unwrap()`과 같은 일을 하지만,
메시지를 취한다는 점이 약간 다릅니다. 이 메시지는 그 밑에 있는 `panic!`까지
전달될 것이며, 코드에 오류가 발생하는 경우에 좀 더 나은 에러 메시지를
제공할 것입니다.

# `try!`의 사용

`Result` 타입을 반환하는 함수들을 많이 호출하는 코드를 작성할 때, 에러 처리는
지루할 수 있습니다. The `try!` macro hides some of the boilerplate
of propagating errors up the call stack
매크로는 호출 스택의 상위로 전달되는 판에 박힌 에러의 일부분을 숨깁니다.

아래의 코드는:

```rust
use std::fs::File;
use std::io;
use std::io::prelude::*;

struct Info {
name: String,
age: i32,
rating: i32,
}

fn write_info(info: &Info) -> io::Result<()> {
let mut file = File::create("my_best_friends.txt").unwrap();

if let Err(e) = writeln!(&mut file, "name: {}", info.name) {
return Err(e)
}
if let Err(e) = writeln!(&mut file, "age: {}", info.age) {
return Err(e)
}
if let Err(e) = writeln!(&mut file, "rating: {}", info.rating) {
return Err(e)
}

return Ok(());
}
```

이렇게 대체될 수 있습니다:

```rust
use std::fs::File;
use std::io;
use std::io::prelude::*;

struct Info {
name: String,
age: i32,
rating: i32,
}

fn write_info(info: &Info) -> io::Result<()> {
let mut file = try!(File::create("my_best_friends.txt"));

try!(writeln!(&mut file, "name: {}", info.name));
try!(writeln!(&mut file, "age: {}", info.age));
try!(writeln!(&mut file, "rating: {}", info.rating));

return Ok(());
}
```

`try!`로 둘러싸인 수식은 그 결과가 `Err`이 아닌 이상 둘러쌓이지 않은
성공(`OK`)값을 반환하게 됩니다. 그 결과가 `Err`인 경우에는 안쪽 둘러싸인
함수에서 넘겨받은 `Err`을 반환하게 됩니다.

이것은 `Result`를 리턴하는 함수에 대해 `try!`를 사용할때에만 유용한데, 이것은
아무것도 반환하지 않는 `main()`함수의 안에서는 `try!`를 사용할 수 없음을 의미합니다.

`try!`은 에러가 발생한 경우에, 무엇을 반환할지를 결정하기 위해 [`From`](../std/convert/trait.From.html)을 이용합니다.