【问题标题】:What design pattern does the SNAFU library use to extend external types?SNAFU 库使用什么设计模式来扩展外部类型?
【发布时间】:2019-09-18 14:37:36
【问题描述】:

我一直在玩有趣的SNAFU library

来自 SNAFU 页面的稍作修改的独立示例如下:

use snafu::{ResultExt, Snafu};
use std::{fs, io, path::PathBuf};

#[derive(Debug, Snafu)]
enum Error {
    #[snafu(display("Unable to read configuration from {}: {}", path.display(), source))]
    ReadConfiguration { source: io::Error, path: PathBuf },
}

type Result<T, E = Error> = std::result::Result<T, E>;

fn process_data() -> Result<()> {
    let path = "config.toml";
    let read_result: std::result::Result<String, io::Error> = fs::read_to_string(path);    
    let _configuration = read_result.context(ReadConfiguration { path })?;
    Ok(())
}

fn main() {
    let foo = process_data();

    match foo {
        Err(e) => println!("Hello {}", e),
        _ => println!("success")
    }
}

我所做的更改是使Result 上的类型从fs::read_to_string(path)process_data() 中显式化。

鉴于此,我无法理解 read_result 如何使用 context 方法,因为 std::result::Result 文档没有对上下文进行任何引用(如果您去掉SNAFU 的东西并尝试访问上下文)。

这里使用的模式对我来说并不明显。我幼稚的理解是,由于孤儿规则,无法扩展外部类型,但这里发生的事情看起来很像这样的扩展。

我也对type Result... 行感到困惑。我知道类型别名,但不使用左侧分配了泛型的语法。显然,这是设计模式的重要组成部分。

我的要求是澄清这里使用的是什么模式以及它是如何工作的。它似乎了解了 Rust 的一些非常有趣的方面。值得进一步阅读!

【问题讨论】:

  • 很高兴你喜欢它!
  • @Shepmaster 是的,它有一个非常优雅的 API!

标签: design-patterns rust idioms


【解决方案1】:

ResultExt 是一个扩展特征,这就是为什么它使用了比较常见的后缀Ext

简化,实现包含特征的定义和特定类型(或仅一种)的特征的一小部分实现:

pub trait ResultExt<T, E>: Sized {
    fn context<C, E2>(self, context: C) -> Result<T, E2>
    where
        C: IntoError<E2, Source = E>,
        E2: std::error::Error + ErrorCompat;
}

impl<T, E> ResultExt<T, E> for std::result::Result<T, E> {
    fn context<C, E2>(self, context: C) -> Result<T, E2>
    where
        C: IntoError<E2, Source = E>,
        E2: std::error::Error + ErrorCompat,
    {
        self.map_err(|error| context.into_error(error))
    }
}

通过导入 ResultExt,您可以将 trait 及其方法带入作用域。该库已经为 Result 类型实现了它们,因此您也可以使用它们。

另见:

我知道类型别名,但没有使用左侧分配泛型的语法。显然,这是设计模式的重要组成部分。

使用.context() 的能力并不重要,这只是我鼓励的一种做法。大多数自定义 Result 别名会隐藏名称 Result(例如 std::io::Result),这意味着如果您需要使用不同的错误类型,则需要使用丑陋的完整路径或其他别名(例如 type StdResult&lt;T, E&gt; = std::result::Result&lt;T, E&gt;)。

通过使用默认泛型创建本地别名Result,用户可以键入Result&lt;T&gt;,而不是更冗长的Result&lt;T, MyError&gt;,但仍可以在需要时使用Result&lt;T, SomeOtherError&gt;。有时,我什至会更进一步,为成功类型定义一个默认类型。这在单元测试中最常见:

mod test {
    type Result<T = (), E = Box<dyn std::error::Error>> = std::result::Result<T, E>;

    fn setup() -> Result<String> {
        Ok(String::new())
    }

    fn special() -> Result<String, std::io::Error> {
        std::fs::read_to_string("/etc/hosts")
    }

    #[test]
    fn x() -> Result {
        Ok(())
    }
}

【讨论】:

  • 啊,有道理。所以snafu 扩展std::result::Result,为了绕过孤儿规则,有必要导入ResultExt,所以扩展特征存在于本地范围内。
猜你喜欢
  • 1970-01-01
  • 2019-12-31
  • 1970-01-01
  • 2018-06-23
  • 1970-01-01
  • 2011-02-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多