SNAFU 库使用什么设计模式来扩展外部类型?
What design pattern does the SNAFU library use to extend external types?
我一直在玩有趣的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")
}
}
我所做的更改是使 fs::read_to_string(path)
中 Result
的类型在 process_data()
中显式显示。
鉴于此,我无法理解 read_result
如何使用 context
方法,因为 std::result::Result
文档没有对上下文(以及如果您删除 SNAFU 内容并尝试访问上下文,编译器同样会抱怨。
这里使用的模式对我来说并不明显。我天真的理解是,由于孤立规则,外部类型无法扩展,但这里发生的事情看起来很像这样的扩展。
我也对 type Result...
行感到困惑。我知道类型别名,但没有使用左侧分配了泛型的语法。显然,这是设计模式的重要组成部分。
我的要求是澄清这里使用的是什么模式以及它是如何工作的。它似乎触及了 Rust 的一些非常有趣的方面。进一步阅读将是有价值的!
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
,您将特性及其方法引入范围。该库已为 Result
类型实现了它们,因此您也可以使用它们。
另请参阅:
I'm aware of type aliasing, but not using the syntax in which the left hand side has a generic assigned. Clearly, this is an important part of the design pattern.
使用 .context()
的能力并不重要,这只是我鼓励的一种练习。大多数自定义 Result
别名都会隐藏名称 Result
(例如 std::io::Result
),这意味着如果您需要使用不同的错误类型,则需要使用丑陋的完整路径或其他别名(例如 type StdResult<T, E> = std::result::Result<T, E>
).
通过使用 默认泛型 创建本地别名 Result
,用户可以键入 Result<T>
而不是更冗长的 Result<T, MyError>
,但仍可在需要时使用 Result<T, SomeOtherError>
。有时,我什至会更进一步,为成功类型定义一个默认类型。这在单元测试中最常见:
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 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")
}
}
我所做的更改是使 fs::read_to_string(path)
中 Result
的类型在 process_data()
中显式显示。
鉴于此,我无法理解 read_result
如何使用 context
方法,因为 std::result::Result
文档没有对上下文(以及如果您删除 SNAFU 内容并尝试访问上下文,编译器同样会抱怨。
这里使用的模式对我来说并不明显。我天真的理解是,由于孤立规则,外部类型无法扩展,但这里发生的事情看起来很像这样的扩展。
我也对 type Result...
行感到困惑。我知道类型别名,但没有使用左侧分配了泛型的语法。显然,这是设计模式的重要组成部分。
我的要求是澄清这里使用的是什么模式以及它是如何工作的。它似乎触及了 Rust 的一些非常有趣的方面。进一步阅读将是有价值的!
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
,您将特性及其方法引入范围。该库已为 Result
类型实现了它们,因此您也可以使用它们。
另请参阅:
I'm aware of type aliasing, but not using the syntax in which the left hand side has a generic assigned. Clearly, this is an important part of the design pattern.
使用 .context()
的能力并不重要,这只是我鼓励的一种练习。大多数自定义 Result
别名都会隐藏名称 Result
(例如 std::io::Result
),这意味着如果您需要使用不同的错误类型,则需要使用丑陋的完整路径或其他别名(例如 type StdResult<T, E> = std::result::Result<T, E>
).
通过使用 默认泛型 创建本地别名 Result
,用户可以键入 Result<T>
而不是更冗长的 Result<T, MyError>
,但仍可在需要时使用 Result<T, SomeOtherError>
。有时,我什至会更进一步,为成功类型定义一个默认类型。这在单元测试中最常见:
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(())
}
}