罪丶 發表於 2024-6-13 10:02:45

Rust anyhow 简明示例教程

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>anyhow::Error</li><li>anyhow::Result</li><li>3 个核心使用技巧</li><li>实战案例</li></ul></div><p>anyhow 是 Rust 中的一个库,旨在提供灵活的、具体的错误处理能力,建立在 <code>std::error::Error</code> 基础上。它主要用于那些需要简单错误处理的应用程序和原型开发中,尤其是在错误类型不需要被严格区分的场景下。</p>
<p>以下是 <code>anyhow</code> 的几个关键特性:</p>
<ul><li><strong>易用性</strong>: <code>anyhow</code> 提供了一个 <code>Error</code> 类型,这个类型可以包含任何实现了 <code>std::error::Error</code> 的错误。这意味着你可以使用 <code>anyhow::Error</code> 来包装几乎所有类型的错误,无需担心具体的错误类型。</li><li><strong>简洁的错误链</strong>: <code>anyhow</code> 支持通过 <code>?</code> 操作符来传播错误,同时保留错误发生的上下文。这让错误处理更加直观,同时还能保留错误链,便于调试。</li><li><strong>便于调试</strong>: <code>anyhow</code> 支持通过 <code>{:#}</code> 格式化指示符来打印错误及其所有相关的上下文和原因,这使得调试复杂的错误链变得更加简单。</li><li><strong>无需关心错误类型</strong>: 在很多情况下,特别是在应用程序的顶层,你可能不需要关心错误的具体类型,只需要知道出错了并且能够将错误信息传递给用户或日志。<code>anyhow</code> 让这一过程变得简单,因为它可以包装任何错误,而不需要显式地指定错误类型。</li></ul>
<p>使用 <code>anyhow</code> 的典型场景包括快速原型开发、应用程序顶层的错误处理,或者在库中作为返回错误类型的一个简便选择,尤其是在库的使用者不需要关心具体错误类型的时候。</p>
<p class="maodian"></p><h2>anyhow::Error</h2>
<p><code>anyhow::Error</code> 是 <code>anyhow</code> 库定义的一个错误类型。它是一个包装器(wrapper)类型,可以包含任何实现了 <code>std::error::Error</code> trait 的错误类型。这意味着你可以将几乎所有的错误转换为 <code>anyhow::Error</code> 类型,从而在函数之间传递,而不需要在意具体的错误类型。这在快速原型开发或应用程序顶层错误处理中特别有用,因为它简化了错误处理的逻辑。</p>
<p>它的定义如下:</p>
<div class="jb51code"><pre class="brush:plain;">#
pub struct Error {
    inner: Own&lt;ErrorImpl&gt;,
}</pre></div>
<p>其中核心是 <code>ErrorImpl</code>:</p>
<div class="jb51code"><pre class="brush:plain;">#
pub(crate) struct ErrorImpl&lt;E = ()&gt; {
    vtable: &amp;'static ErrorVTable,
    backtrace: Option&lt;Backtrace&gt;,
    // NOTE: Don't use directly. Use only through vtable. Erased type may have
    // different alignment.
    _object: E,
}</pre></div>
<p><code>ErrorImpl</code> 是一个内部结构体,用于实现 <code>anyhow::Error</code> 类型的具体功能。它包含了三个主要字段:</p>
<ul><li><code>vtable</code> 是一个指向静态虚拟表的指针,用于动态派发错误相关的方法。</li><li><code>backtrace</code> 是一个可选的回溯(Backtrace)类型,用于存储错误发生时的调用栈信息。</li><li><code>_object</code> 字段用于存储具体的错误对象,其类型在编译时被擦除以提供类型安全的动态错误处理。</li></ul>
<p>这种设计允许 <code>anyhow</code> 错误封装并表示各种不同的错误类型,同时提供了方法动态派发和回溯功能,以便于错误调试。</p>
<p><code>anyhow::Error</code> 可以包含任何实现了 <code>std::error::Error</code> trait 的错误类型,这里因为下面的 <code>impl</code>:</p>
<div class="jb51code"><pre class="brush:plain;">impl&lt;E&gt; StdError for ErrorImpl&lt;E&gt;
where
    E: StdError,
{
    fn source(&amp;self) -&gt; Option&lt;&amp;(dyn StdError + 'static)&gt; {
      unsafe { ErrorImpl::error(self.erase()).source() }
    }
    #
    fn provide&lt;'a&gt;(&amp;'a self, request: &amp;mut Request&lt;'a&gt;) {
      unsafe { ErrorImpl::provide(self.erase(), request) }
    }
}</pre></div>
<p class="maodian"></p><h2>anyhow::Result</h2>
<p><code>anyhow::Result</code> 是一个别名(type alias),它是 <code>std::result::Result&lt;T, anyhow::Error&gt;</code> 的简写。在使用 <code>anyhow</code> 库进行错误处理时,你会频繁地看到这个类型。它基本上是标准的 <code>Result</code> 类型,但错误类型被固定为 <code>anyhow::Error</code>。这使得你可以很容易地在函数之间传递错误,而不需要声明具体的错误类型。</p>
<div class="jb51code"><pre class="brush:plain;">pub type Result&lt;T, E = Error&gt; = core::result::Result&lt;T, E&gt;;</pre></div>
<p>使用 <code>anyhow::Result</code> 的好处在于它提供了一种统一的方式来处理错误。你可以使用 <code>?</code> 操作符来传播错误,同时保留错误的上下文信息和回溯。这极大地简化了错误处理代码,尤其是在多个可能产生不同错误类型的操作链中。</p>
<p class="maodian"></p><h2>3 个核心使用技巧</h2>
<ul><li>使用 <code>Result&lt;T, anyhow::Error&gt;</code> 或者 <code>anyhow::Result&lt;T&gt;</code> 作为返回值,然后利用 <code>?</code> 语法糖无脑传播报错。</li><li>使用 with_context(f) 来附加错误信息。</li><li>使用 downcast 反解具体的错误类型。</li></ul>
<p class="maodian"></p><h2>实战案例</h2>
<p>下面我们用一个案例来体会 <code>anyhow</code> 的使用方式:</p>
<p>我们的需求是:打开一个文件,解析文件中的数据并进行大写化,然后输出处理后的数据。</p>
<div class="jb51code"><pre class="brush:plain;">use anyhow::{Result, Context};
use std::{fs, io};
// 1. 读取文件、解析数据和执行数据操作都可能出现错误,
// 所以我们需要返回 Result 来兼容异常情况。
// 这里我们使用 anyhow::Result 来简化和传播错误。
fn read_and_process_file(file_path: &amp;str) -&gt; Result&lt;()&gt; {
    // 尝试读取文件
    let data = fs::read_to_string(file_path)
      // 2. 使用 with_context 来附加错误信息,然后利用 ? 语法糖传播错误。
      .with_context(||format!("failed to read file `{}`", file_path))?;
    // 解析数据
    let processed_data = parse_data(&amp;data)
      .with_context(||format!("failed to parse data from file `{}`", file_path))?;
    // 执行数据操作
    perform_some_operation(processed_data)
      .with_context(|| "failed to perform operation based on file data")?;
    Ok(())
}
fn parse_data(data: &amp;str) -&gt; Result&lt;String&gt; {
    Ok(data.to_uppercase())
}
fn perform_some_operation(data: String) -&gt; Result&lt;()&gt; {
    println!("processed data: {}", data);
    Ok(())
}
fn main() {
    let file_path = "./anyhow.txt";
        // 执行处理逻辑
    let res =read_and_process_file(file_path);
        // 处理结果
    match res {
      Ok(_) =&gt; println!("successfully!"),
      Err(e) =&gt; {
            // 3. 使用 downcast 来反解出实际的错误实例,本案例中可能出现的异常是 io::Error。
            if let Some(my_error) = e.downcast_ref::&lt;io::Error&gt;() {
                println!("has io error: {:#}", my_error);
            } else {
                println!("unknown error: {:?}", e);
            }
      }
    }
}</pre></div>
<p>到此这篇关于Rust anyhow 简明教程的文章就介绍到这了,更多相关Rust anyhow内容请搜索琼殿技术社区以前的文章或继续浏览下面的相关文章希望大家以后多多支持琼殿技术社区!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>详解Rust语言中anyhow的使用</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: Rust anyhow 简明示例教程