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