Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

10. 에러 처리 패턴 🟢

학습 목표:

  • 라이브러리용 thiserror와 애플리케이션용 anyhow를 구분하여 사용하는 법을 배웁니다.
  • #[from].context()를 이용해 에러 변환 체인을 구축하는 기술을 익힙니다.
  • ? 연산자의 내부 작동 방식과 main() 함수에서의 활용법을 이해합니다.
  • 예상된 에러(Result)와 프로그래밍 버그(panic)를 명확히 구분합니다.

thiserror vs anyhow: 라이브러리냐 애플리케이션이냐

Rust의 에러 처리는 Result<T, E>를 중심으로 돌아갑니다. 상황에 따라 다음 두 크레이트 중 하나를 선택하세요.

  • thiserror (라이브러리용): 구체적인 에러 열거형을 정의할 때 사용합니다. 호출자가 에러 종류를 match로 구분해야 하는 라이브러리에 적합합니다.
  • anyhow (애플리케이션용): 에러의 구체적인 종류보다 "실패했다"는 사실과 원인 파악이 더 중요한 최종 실행 파일에 적합합니다. .context() 기능이 아주 강력합니다.
구분thiserroranyhow
권장 용도공유 라이브러리, 크레이트최종 바이너리, 애플리케이션
에러 타입구체적인 열거형 (패턴 매칭 가능)불투명한 anyhow::Error
장점호출자가 정교한 에러 대응 가능코드 작성이 빠르고 컨텍스트 추가 용이

에러 변환 체인 (#[from])

thiserror#[from] 속성을 사용하면 ? 연산자가 하위 에러를 상위 에러로 자동 변환해 줍니다.

#![allow(unused)]
fn main() {
#[derive(Error, Debug)]
pub enum AppError {
    #[error("I/O 에러: {0}")]
    Io(#[from] std::io::Error), // io::Error를 AppError::Io로 자동 변환

    #[error("JSON 에러: {0}")]
    Json(#[from] serde_json::Error),
}
}

컨텍스트과 에러 래핑 (anyhow)

anyhow는 에러가 발생한 지점의 상황을 설명하는 문구(Context)를 겹겹이 쌓아 올릴 수 있게 해줍니다.

#![allow(unused)]
fn main() {
let content = std::fs::read_to_string(path)
    .with_context(|| format!("{path} 파일을 읽는 데 실패했습니다"))?;
}

출력 결과: "파일 읽기 실패" -> "원인: 권한 없음(OS Error)" 순서로 에러 원인들이 체인처럼 출력되어 디버깅이 매우 쉬워집니다.


? 연산자의 깊은 이해

?는 단순히 에러를 반환하는 것 이상의 일을 합니다. 내부적으로 From 트레이트를 호출하여 정의된 에러 타입으로 자동 변환까지 수행합니다.

#![allow(unused)]
fn main() {
// 이 코드는...
let value = operation()?;

// 내부적으로 다음과 같습니다:
let value = match operation() {
    Ok(v) => v,
    Err(e) => return Err(From::from(e)), // 자동으로 에러 타입을 변환하여 반환
};
}

패닉(Panic) vs 에러(Result)

  • Result<T, E>: 파일 없음, 네트워크 타임아웃 등 충분히 발생할 수 있는 실패 상황에 사용하세요.
  • panic!(): 인덱스 범위를 벗어남, 절대 일어나면 안 되는 논리적 모순 등 프로그래밍 버그를 나타낼 때만 사용하세요.
  • catch_unwind: C/C++ 라이브러리와 통신(FFI)하거나 스레드 풀 경계에서 패닉이 프로그램 전체를 죽이지 않도록 격리할 때 사용합니다.

📝 연습 문제: thiserror를 활용한 에러 계층 설계 ★★ (~30분)

파일 처리 앱을 위한 에러 구조를 설계해 보세요. I/O 에러, 파싱 에러(JSON), 그리고 사용자 비즈니스 검증 에러가 포함되어야 합니다. ? 연산자가 어떻게 각 에러를 AppError로 변환하는지 코드로 구현해 보세요.


📌 요약

  • 라이브러리는 thiserror, 애플리케이션은 **anyhow**를 쓰세요.
  • **.context()**를 활용해 에러 발생 상황을 구체적으로 기록하세요.
  • **?**는 에러 전파뿐 아니라 자동 타입 변환기이기도 합니다.
  • 패닉은 대처 가능한 에러 처리에 쓰는 도구가 아닙니다.