Post thumbnail

Rust 에러 처리! Anyhow 몰?루

· by 박승재

Anyhow일반화Error 타입을 이용해 코드에서 발생하는 에러를 한 가지 타입으로 처리할 수 있게 도와주는 라이브러리입니다.

any·how

  1. = anyway
  2. 되는대로, 아무렇게나

출처: 네이버 영어사전

Rust에서는 엄격하게 타입을 검사하기 때문에, 여러 라이브러리에서 사용하는 서로 다른 에러 타입을 처리하기 위해서는 map_err로 에러를 변환해 통일하거나 Box<dyn std::error::Error>를 이용해 타입을 뭉개버리는 방법을 사용합니다.

“타입을 뭉갠다”는 타입을 일반화하거나 타입의 제약조건을 덜어내는 것을 의미합니다.

map_err을 사용할 때는, 문자열로 에러를 변환하거나 새로운 에러 타입을 만들어 통일합니다.

fn stringify(x: u32) -> String { format!("error code: {}", x) }

let x: Result<u32, u32> = Err(404);
x.map_err(stringify); // error code: 404

Box<dyn std::error::Error>는 “std::error::Error 트레잇(Trait)을 구현한 타입이면 아무거나 된다”라는 의미입니다.

트레잇이기 때문에 dyn 키워드가 붙었고, 에러 타입의 크기를 정확히 모르기 때문에 Box로 감싸줍니다.

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // ...
}

마찬가지로, Anyhow도 Box<dyn std::error::Error>동일하게 사용하면 됩니다.

fn get_cluster_info() -> anyhow::Result<ClusterMap> {
    let config = std::fs::read_to_string("cluster.json")?;
    let map: ClusterMap = serde_json::from_str(&config)?;
    Ok(map)
}

anyhow::ErrorBox<dyn std::error::Error>와 매우 비슷하지만, 몇 가지 차이점이 있습니다.

  • ErrorSend, Sync, 'static이어야 합니다.

    즉, 스레드간 소유권 이전(Send)과 여러 스레드에서 접근(Sync)이 가능해야 합니다.

    또한, Error가 가지고 있는 참조는 항상 'static이어야 합니다.

  • Error역추적(Backtrace)이 보장됩니다.

에러를 출력할 때 사용하는 포맷(Format)도 더 자세한 정보를 제공합니다.

{:#}:

Failed to read instrs from ./path/to/instrs.json: No such file or directory (os error 2)

{:?}:

Error: Failed to read instrs from ./path/to/instrs.json

Caused by:
    No such file or directory (os error 2)

{:#?}:

Error {
    context: "Failed to read instrs from ./path/to/instrs.json",
    source: Os {
        code: 2,
        kind: NotFound,
        message: "No such file or directory",
    },
}

anyhow! 매크로는 문자열에서 에러를 만들 때 사용합니다.

return Err(anyhow!("Missing attribute: {}", missing)); 

bail!은 에러를 조기에 반환할 때 사용합니다.

return Err(anyhow!("error message"))동일한 동작을 합니다.

if !has_permission(user, resource) {
    bail!("permission denied for accessing {}", resource);
}