Rust 에러 핸들링

Rust 에러 핸들링

날짜
생성자
ShalomShalom
카테고리
rust
작성일
2023년 05월 15일
태그
rust
이전 블로그

에러 핸들링

모든 프로그램의 경우 실패할 수 있는 방법이 있습니다.
가장 단순한 프로그램을 제외한...

대표적인 에러

실패할 수 있는 코드를 작성할 때 가장 중요한 점은 사용자가 반환된 오류에 대처하냐입니다.
조건은 다음과 같습니다.
  1. 정확히 어떤 오류가 일어 났는지, 또한 세부사항에 대해서 알 수 있어야합니다.
  1. 오류 유형에 열거할 수 있어야합니다.
  1. 호출자가 해당 조건을 구별할 수 있도록 또는 사용자가 해당 조건을 제공할 수 있습니다.

열거

byte로 복사하는 std::io::copy 라이브러리를 예를 들겠습니다.
  1. 읽기 위한 스트림과 쓰기 위한 스트림이 있습니다.
  1. 스트림을 바이트를 복사하는 과정에서 얼마든지 실패할 수 있습니다.
  1. 실패한 경우 복사가 중지되고 호출자에게 반환합니다.
  1. 에러의 종류에 따라서 호출자는 다음 작업을 수행할 지, 프로그램을 종료할 지 등을 정할 수 있습니다.
이에 따라 CopyCopyError 라는 열거를 통해 사용자에게 오류에 대한 원인을 나타내 줍니다.
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", } } }
위의 예시를 추가해서 설명을 해보겠습니다.
  1. 에러 타입은 근원적인 에러까지 추적이 가능해야합니다.
  1. 에러의 디스플레이 문구는 소문자로만 이루어 져야합니다.
  1. 에러의 디스플레이에서 보조 정보를 보다 서술적으로 표시를 해줄 수 있어야합니다.

에러타입은 Send와 Sync를 모두 구현해야합니다.

  1. 에러의 경우 스레드의 사이에서 오류를 공유를 할 경우가 많습니다.
  1. 따라서 Rc나 RefCell의 타입을 Error안에 배치하기전에 확인을 해야합니다.
  1. 모든 오류 유형은 static이어야 합니다.
    1. 즉작적으로 호출자가 error에 대해 더 쉽게 전파해야 함
    2. 따라서 lifetime에 구애받지 않고 사용할 수 있어야함
      1. lifetime을 static으로 해야하는 것을 권장
        1. downcasting이 가능하기 때문
        2. 추적의 용이

불투명 오류

이미지 디코딩 라이브러리를 예를 들어 보겠습니다.
  1. 이미지 디코딩시 다양한 이미지 조작 방법에 엑세스가 가능합니다.
  1. 디코딩 실패시 에러를 반환해야합니다.
    1. 원인이 이미지의 크기 필드인 경우
    2. 헤더가 잘못 된 경우
    3. 압축 푸는 알고리즘이 실패한 경우
    4. 블록?(아닐 확률이 높음)
  1. 정확한 원인을 알고 있더라도 어플리케이션의 단계에서 복구할 수 없는 경우가 발생
따라서 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 }

댓글

guest