anyhow, thiserror 등 에러 라이브러리

Rust에서 에러 처리를 조금 더 편리하고 유연하게 하기 위해서는 타사가 제공하는 라이브러리를 활용하는 방법이 유용하다. 특히 크게 두 가지 접근이 널리 쓰인다. 하나는 에러를 구체적으로 정의하기보다는 좀 더 범용적으로 다루기 위해서 anyhow 라이브러리를 사용하는 것이고, 다른 하나는 사용자가 직접 에러 타입을 작성하면서도 매크로를 통해 반복적인 코드를 줄여주는 thiserror 라이브러리를 사용하는 방식이다. 이 두 라이브러리를 적절히 이해하고 선택해 활용하면 에러 처리의 생산성과 가독성을 크게 높일 수 있다.

anyhow는 애플리케이션 전반에서 에러를 간단히 모아서 다루고자 할 때 적합하다. 구체적인 에러 타입보다는 다양한 에러가 모두 anyhow::Error 안에 담길 수 있도록 설계되었다. 이런 설계는 리턴 타입을 단순화하며, 실제 로직에서 발생할 수 있는 여러 가지 에러를 일일이 열거하기 어려울 때 유용하다. 또한 ? 연산자로 발생한 에러를 손쉽게 전파할 수 있고, 에러를 생성하는 헬퍼 매크로인 anyhow::bail!이나 anyhow::ensure! 같은 유틸리티를 제공하기 때문에 편의성이 매우 높다. 따라서 간단한 프로젝트나 빠른 프로토타이핑, 도구성 애플리케이션 등을 작성할 때는 anyhow를 사용하는 것이 일반적이다.

예시를 살펴보면 다음과 같다.

[dependencies]
anyhow = "1.0"
use anyhow::{Result, Context};

fn read_file_contents(path: &str) -> Result<String> {
    let contents = std::fs::read_to_string(path)
        .with_context(|| format!("파일을 읽어오는 도중 에러가 발생하였다: {}", path))?;
    Ok(contents)
}

fn main() -> Result<()> {
    let file_data = read_file_contents("example.txt")?;
    println!("{}", file_data);
    Ok(())
}

위 예시에서는 Result<T, anyhow::Error>를 리턴 타입으로 사용한다. with_context 메서드를 통해 에러가 발생했을 때 맥락(Context)을 함께 제공할 수 있어 에러 메시지가 좀 더 풍부해진다. 또한 main 함수 역시 -> Result<()> 형태로 작성해주면, 발생하는 에러를 ? 연산자를 사용해 간단히 전파하고 종료 시점에 적절히 처리하도록 할 수 있다.

만약 보다 구체적이고 의미 있는 에러 타입을 설계해야 하는 상황이라면 thiserror가 적절하다. thiserror는 에러 타입을 직접 정의하되, 각 에러 변형(variant)에 대해 에러 메시지와 표시 형식을 간단히 설정할 수 있는 매크로를 제공한다. 특히 라이브러리를 작성할 때나, 다양한 범주의 에러를 체계적으로 정의해두고 싶을 때 많이 쓰인다. thiserror를 통해 도출된 사용자 정의 에러 타입은 패턴 매칭으로 구체적인 에러 종류를 구분하기 좋고, 각 상황에 대한 핸들링 로직을 명확히 작성할 수 있다.

예시를 살펴보면 다음과 같다.

#[derive(Error, Debug)] 애트리뷰트로부터 thiserror의 기능을 활용하게 되며, 각 변형(variant)마다 #[error("...")] 애트리뷰트를 통해 에러 메시지를 작성할 수 있다. 이렇게 작성한 에러 타입은 매번 String 기반의 메시지를 생성하는 방식보다 훨씬 체계적으로 관리할 수 있고, 코드가 길어질수록 중요성이 더 커진다. 또한 에러 자체를 구분하기가 쉬워지고, match 등을 통해 패턴 매칭을 할 때도 각 변형마다 다른 로직을 적용하기 수월해진다.

종합하면 anyhowthiserror는 각각 쓰임새가 다르지만, 목적에 따라 매우 효과적인 에러 처리 방식을 제공한다. 빠른 프로토타이핑이나 단순한 애플리케이션에서는 anyhow의 범용적인 에러 타입을 사용하여 빠르게 개발을 진행할 수 있고, 보다 체계적으로 에러 타입을 정의해야 하거나 라이브러리를 작성하는 과정에서는 thiserror를 통해 에러를 명확히 선언하고 유지보수성을 높일 수 있다. 상황에 맞춰 이 두 라이브러리를 적절히 활용하는 것이 올바른 에러 처리 전략으로 이어진다.

Last updated