에러 핸들링
모든 프로그램의 경우 실패할 수 있는 방법이 있습니다.
가장 단순한 프로그램을 제외한...
대표적인 에러
실패할 수 있는 코드를 작성할 때 가장 중요한 점은 사용자가 반환된 오류에 대처하냐입니다.
조건은 다음과 같습니다.
- 정확히 어떤 오류가 일어 났는지, 또한 세부사항에 대해서 알 수 있어야합니다.
- 오류 유형에 열거할 수 있어야합니다.
- 호출자가 해당 조건을 구별할 수 있도록 또는 사용자가 해당 조건을 제공할 수 있습니다.
열거
byte로 복사하는
std::io::copy
라이브러리를 예를 들겠습니다.- 읽기 위한 스트림과 쓰기 위한 스트림이 있습니다.
- 스트림을 바이트를 복사하는 과정에서 얼마든지 실패할 수 있습니다.
- 실패한 경우 복사가 중지되고 호출자에게 반환합니다.
- 에러의 종류에 따라서 호출자는 다음 작업을 수행할 지, 프로그램을 종료할 지 등을 정할 수 있습니다.
이에 따라
Copy
는 CopyError
라는 열거를 통해 사용자에게 오류에 대한 원인을 나타내 줍니다.pub enum CopyError { In(std::io::Error), Out(std::io::Error), }
읽기와 쓰기 각각 어디에서
io::Error
가 나왔는지 알 수가 있다.error type은 std::error를 구현해야합니다.
#[derive(Debug, PartialEq, Eq, Clone)] #[stable(feature = "env", since = "1.0.0")] pub enum VarError { /// The specified environment variable was not present in the current /// process's environment. #[stable(feature = "env", since = "1.0.0")] NotPresent, /// The specified environment variable was found, but it did not contain /// valid unicode data. The found data is returned as a payload of this /// variant. #[stable(feature = "env", since = "1.0.0")] NotUnicode(#[stable(feature = "env", since = "1.0.0")] OsString), } #[stable(feature = "env", since = "1.0.0")] impl Error for VarError { #[allow(deprecated)] fn description(&self) -> &str { match *self { VarError::NotPresent => "environment variable not found", VarError::NotUnicode(..) => "environment variable was not valid unicode", } } }
위의 예시를 추가해서 설명을 해보겠습니다.
- 에러 타입은 근원적인 에러까지 추적이 가능해야합니다.
- 에러의 디스플레이 문구는 소문자로만 이루어 져야합니다.
- 에러의 디스플레이에서 보조 정보를 보다 서술적으로 표시를 해줄 수 있어야합니다.
에러타입은 Send와 Sync를 모두 구현해야합니다.
- 에러의 경우 스레드의 사이에서 오류를 공유를 할 경우가 많습니다.
- 따라서 Rc나 RefCell의 타입을 Error안에 배치하기전에 확인을 해야합니다.
- 모든 오류 유형은 static이어야 합니다.
- 즉작적으로 호출자가 error에 대해 더 쉽게 전파해야 함
- 따라서 lifetime에 구애받지 않고 사용할 수 있어야함
- lifetime을 static으로 해야하는 것을 권장
- downcasting이 가능하기 때문
- 추적의 용이
불투명 오류
이미지 디코딩 라이브러리를 예를 들어 보겠습니다.
- 이미지 디코딩시 다양한 이미지 조작 방법에 엑세스가 가능합니다.
- 디코딩 실패시 에러를 반환해야합니다.
- 원인이 이미지의 크기 필드인 경우
- 헤더가 잘못 된 경우
- 압축 푸는 알고리즘이 실패한 경우
- 블록?(아닐 확률이 높음)
- 정확한 원인을 알고 있더라도 어플리케이션의 단계에서 복구할 수 없는 경우가 발생
따라서 API는 단일의 불투명한 오류를 제공하는 것이 적합할 수 있습니다.
즉, 호출자가 수정할 수 없는 오류에 대해서는 세세히 처리를 하지 않도록 유도하는게 좋다.
특수한 에러 케이스
의미가 있는 오류를 None으로 반환하여서는 안됩니다.
일반적으로 반환 유형은
Result<T,()>
입니다. 이것을 Option<T>
로 대체하여 표시할 수 있습니다.이러한 함수의 반환 유형에 대해 둘다 적합한 선택입니다.
다만
Result<T,()>
의 의미를 반환하여야 하는 상황에서 Option<T>
로 단순화하여 반환하는 작업을 피해야합니다.Err()
: 작업이 실패했음을 직관적으로 표현할 수 있습니다.None
: 은 반환할 항목이 없음을 표현합니다.Error에 특성을 구현 하지 말아야합니다.
예를 들어
Box<dyn Error>
와 같이 Error에 특성을 집어 넣게되면 고통이 시작됩니다.차라리 다른 오류를 사용하세요.
Never type
에러가 났을 경우 프로그램을 종료 시킬 수 있는 API를 만들때 사용됩니다.
Result<T,!>
는 다른 Result문과 유사하지만 Err를 반환할 수 없고 panic을 호출합니다.$\large\color{black}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━$
오류 전파
Result<T, E>
를 반환하는 함수에서 모든 결과<T, X>에서 Error를 지우는 작업을 수행하게 되고, 이로 인해 특적 오류 유형에 대해 걱정할 필요가 없으며 동작
을 하게 됩니다.try
fn do_the_thing() -> Result<(), Error> { let thing = Thing::setup()?; // .. code that uses thing and ? .. thing.cleanup(); Ok(()) }
fn do_the_thing() -> Result<(), Error> { let thing = Thing::setup()?; let r = try { // .. code that uses thing and ? .. }; thing.cleanup(); r }
댓글