Rust错误处理


file:///Users/fliter/rust-in-databend/28-%E7%AC%AC%E4%B8%89%E8%AF%BE-%E5%A6%82%E4%BD%95%E4%BC%98%E9%9B%85%E7%9A%84%E5%A4%84%E7%90%86%E9%94%99%E8%AF%AF/%E7%AC%AC%E4%B8%89%E8%AF%BE%20%E5%A6%82%E4%BD%95%E4%BC%98%E9%9B%85%E7%9A%84%E5%A4%84%E7%90%86%E9%94%99%E8%AF%AF.pdf


如何优雅的处理错误

unwrap()、match()、Err、From、?

在Rust中,unwrap()、match()、Err和From等关于错误处理的概念和方法:

  • unwrap(): 此方法直接解封Result或Option的值,如果是错误值则直接panic。通常用于测试代码。

    1
    let value = Some(5).unwrap(); // 直接得到5
  • match: 通过匹配不同的情况来处理Result或Option的值,可以在不同分支中进行不同处理。

    1
    2
    3
    4
    let value = match Some(5) {
    Some(v) => v,
    None => 0
    };
  • Err: Result枚举的错误值类型。表示某个操作失败。

    1
    2
    3
    fn op() -> Result<i32, std::io::Error> {
    Err(std::io::Error::new(ErrorKind::Other, "error message"))
    }
  • ?: 将结果值传播成一个闭包或函数的返回值。如果错误则直接返回错误。相当于try!的地操作。

    1
    2
    3
    4
    5
    fn process() -> Result<(), String> {
    let value = read_data()?;
    // ...
    Ok(())
    }
  • From: 实现From trait可以在错误类型间进行转换。

    1
    2
    3
    4
    5
    impl From<io::Error> for MyError {
    // ...
    }

    let error: MyError = io_error.into();

所以这些概念搭配使用可以更好地处理Rust中的错误Case,通过Result和match的组合来优雅地处理错误情况。


可以在Rust中使用?操作符来传播错误值,那么它有什么限制吗?


在Rust中使用?操作符来传播错误值时,有以下几个限制:

  • 必须返回Result类型的值:?只能用于返回Result或者实现了Try trait的值。

  • 只能在函数返回值位置:?只能出现在return语句后面或闭包返回位置。

  • 不能静默忽略错误:?不会忽略错误而直接返回,错误会被传播出来。

  • 只能直接返回到外层函数:?不能用于在函数内部的中间变量,只能用于把错误返回到函数体外。

  • 会提前返回:一旦?后面的值为Err,函数将立即返回而不执行后续语句。

  • 不能用于违反 borrow checker 的位置:如在引用的作用域会导致编译错误。

  • 不能内联使用:?不能用于内联函数、写在其他表达式中的地方。

  • 消耗 self 变量:如果在方法中使用?,self变量将被消耗掉。

所以总体来说,?操作符主要限于用于一个返回Result的函数内部,用来简化返回错误的写法。但有的位置如中间变量等还是不能使用。这是?的一些重要限制。



实际项⽬开发中, 到底是否应该避免unwrap()的出现?


上面这样处理是否存在问题?




2024.07.09记录:

Rust错误处理



2024.08.03

【疯狂的Rust库】Anyhow错误处理

我已经按照您的要求整理了内容,保持原文的完整性和顺序。以下是整理后的内容:

【疯狂的Rust库】Anyhow错误处理

Hello,大家好,今天给大家介绍一个Rust错误类型处理库,Anyhow。这个错误类型处理库主要提供了一个Anyhow的类型,你可以把它当作是一个实现了标准库错误trait的智能指针。也就是说,它能够接收所有实现了标准库错误trait的类型。

下面来看一下他给出的第一个小例子。第一个小例子主要介绍的是,它能够很好地跟问号操作符结合在一起使用。使用问号操作符最大的好处,就是可以让你写的代码变得非常简洁明了。类似于这个小例子,如果你读取文件错误的话,它就直接返回;如果没有错误的话,它就往下走。

第二个小例子主要介绍的是,它能够很好地添加额外的错误信息。例如在第一个函数调用detail这里,如果它发生错误的话,就添加一个额外的错误信息在原来的错误信息前面。上面这个调用的是with_context函数,with_context和context的最大区别是with_context接收的是一个闭包。这个闭包可以捕获外面的变量,你这个path就可以被捕获进来,然后当做错误输出的一部分。

下面第三个小例子介绍了,它能够很好地和第三方的错误库进行结合。例如,下面这个错误类型是使用第三方thiserror库来生成的,然后它这里面所有的错误类型,都可以直接返回给anyhow的错误类型使用。

最后上面介绍的是,你如何去定制自己的错误类型。就是说如果函数返回的错误类型并不满足anyhow的类型的话,你可以使用anyhow的宏,对错误信息再次进行包装成anyhow的类型以后,直接返回。这时,它满足anyhow::Result类型的要求。

这个库也是支持no_std的。这个库也是非常受欢迎的,它的下载次数已经达到了1亿多次。



【疯狂的Rust库】thiserror构建错误类型

我已经按照您的要求整理了内容,保持原文的完整性和顺序。以下是整理后的内容:

【疯狂的Rust库】thiserror构建错误类型

Hello,大家好,今天给大家分享一个Rust的错误生成库,thiserror。这个库提供了一个很方便的派生宏,来生成标准库的错误trait。

下面来看一下这个小例子。这个小例子是把所有的错误类型包装到一个枚举类型DataStoreError里面。第一个Disconnect类型的话,它是接收了一个IO Error的类型。同时在派生这个类型的时候,它也会为这个DataStoreError生成一个from IoError的函数,就是说IoError直接调用into函数,生成这个DataStoreError的Disconnect类型。

上面这个#[error]修饰的是它在打印错误的时候,它就直接打印出来上面写的这个”Data store disconnect”信息出来。而第二个类型它是接收一个String的类型,最主要关注的是它在打印错误信息的时候,就花括号里,就是说它会把这里面的String的信息给打印到错误信息上面。

第三个错误类型Header的话,这些错误信息就更加的明了了。在打印错误信息的时候,它会把成员expected的错误信息打印到这里去,found的信息可以打印到这里去。

下面直接来看一下它的运行效果。这边我已经把这个小例子给抄下来了,直接在这边看一下它的运行效果。因为#[error]是由thiserror提供的,所以它这里就只有Disconnect接收一个IoError的类型。然后这里有一个#[from]的标记,就是说它能够接受一个IoError的类型来生成这个Disconnect类型。

所以第一个就是一个Disconnect类型,而它打印的错误信息就只有下面这些:”Data store disconnect”。我第二个错误类型的话就是一个这样的错误类型,它在打印的时候,它也是把这个信息给打印出来。因为我们传入的是一个”hello”的字符串,所以它这里把这个花括号里面的信息替换成”hello”的字符串。12-13的话也是一样的道理,它自己打印出来的信息也是符合我们的期望的。

这个库也是非常受欢迎的,它的下载次数已经达到了1亿多次。同时它已经迭代了59个版本。



Rust Error Handling - Best Practices

好的,我会帮您整理这篇关于Rust错误处理最佳实践的内容,使其更加合理通顺,同时不会遗漏或省略任何内容。以下是整理后的内容:

Rust错误处理 - 最佳实践

错误处理可以分为两类:测试代码和示例的错误,以及生产代码(包括应用程序和模块)的错误。虽然测试代码是应用程序和模块的一部分,但我还是区分了这两种情况下的用法。

对于测试代码:

  • 我想放松一些,不需要很严格
  • 大多数时候使用 ? 操作符就足够了
  • 如果需要更多信息,可以使用静态字符串(str::static)或格式化字符串

对于生产代码:

  • 我想更严格一些
  • 使用自定义的错误结构体,完全控制捕获的内容及方式
  • 使用枚举类型(一种代数数据类型),允许每个变体有不同的类型

在这两种情况下,我都希望能够进步。这意味着:

  • 当在测试端编写代码时,希望能够移动或复制粘贴到生产端
  • 不想在测试和生产之间有完全不同的处理方式,除了错误的类型
  • 在项目开始时稍微放松一些,然后随着项目发展变得更加严格

我的策略是:

  • 大部分时间使用 ? 操作符
  • 有时可能会使用 map_err 或 ok
  • 目标是在最后有一个 ?
  • 不使用 unwrap、expect 或 panic
  • 不使用上下文(context)

我喜欢规范化所有的代码,无论是测试代码还是生产代码,都在结尾使用 ?。这让我可以循序渐进,对双方使用相同的编码风格。

关于模块错误的命名模式:

  • 不在错误中给出完整的描述性名称,因为那是多余的
  • Rust生态系统现在基本上标准化了这种模式
  • 在模块内,通常只是导入 Error
  • 如果是一个新模块,想消除歧义,则导入模块并使用 ModuleName::Error
  • 不在子模块中使用 Error,因为那是多余的

关于资源方面的最佳实践:

  • 有错误的兄弟类型 Result
  • Result 是 T 的类型别名
  • 让 T 尽可能开放
  • 将 Result 类型别名与相应的 Error 放在同一个模块里

现在让我们深入了解一下具体实现:

  1. 在测试代码和项目初期:

首先导入 Box 类型:

1
2
use std::error::Error;
type Result<T> = std::result::Result<T, Box<dyn Error>>;

这给了我们很大的灵活性。例如,一个列出给定路径所有文件的函数:

1
2
3
4
5
6
7
fn list_files(path: &str) -> Result<Vec<String>> {
let files = std::fs::read_dir(path)?
.filter_map(|entry| entry.ok())
.map(|entry| entry.file_name().to_string_lossy().to_string())
.collect();
Ok(files)
}

我们可以这样使用它:

1
let files = list_files(".")?;

这种方法的好处是:

  • 我们可以使用 ? 操作符
  • 我们也可以使用自定义错误,比如静态字符串或格式化字符串:
1
std::fs::read_dir(path).map_err(|e| format!("Failed to read directory: {}", e))?

如果我们有自定义错误逻辑:

1
2
3
if files.is_empty() {
return Err("Cannot list empty folder".into());
}
  1. 在生产代码中:

通常我会创建一个 mod error 文件,保持签名不变,然后重新导出:

1
2
mod error;
pub use self::error::{Error, Result};

在 error.rs 文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use std::fmt;

#[derive(Debug)]
pub enum Error {
Io(std::io::Error),
Custom(String),
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Io(e) => write!(f, "IO error: {}", e),
Error::Custom(s) => write!(f, "{}", s),
}
}
}

impl std::error::Error for Error {}

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

然后在代码中使用:

1
2
3
4
5
6
7
8
9
10
11
12
fn list_files(path: &str) -> Result<Vec<String>> {
let files = std::fs::read_dir(path).map_err(Error::Io)?
.filter_map(|entry| entry.ok())
.map(|entry| entry.file_name().to_string_lossy().to_string())
.collect();

if files.is_empty() {
return Err(Error::Custom("Cannot list empty folder".to_string()));
}

Ok(files)
}
  1. 进一步组织错误:

当错误类型变得复杂时,我们可以按模块组织它们:

1
2
3
4
5
6
7
8
9
pub enum Error {
Fs(FsError),
// 其他模块的错误...
}

pub enum FsError {
CannotListEmptyFolder,
// 其他文件系统相关错误...
}
  1. 错误的序列化和显示:

对于网络应用程序或需要序列化错误的场景,我们可以实现 serde::Serialize trait:

1
2
3
4
#[derive(Debug, Serialize)]
pub enum Error {
// ...
}

这样我们就可以将错误序列化为 JSON 并发送到服务器或日志中。

  1. 在测试中使用简化的错误处理:

在测试模块中,我们可以使用简化的 Result 类型:

1
2
3
4
5
6
7
8
9
10
#[cfg(test)]
mod tests {
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;

#[test]
fn test_list_files() -> Result<()> {
// 测试代码...
Ok(())
}
}

总结:

  • 从简单的错误处理开始,逐步发展到更复杂和严格的错误处理
  • 保持测试代码和生产代码的错误处理风格一致,以便于代码迁移
  • 根据项目需求,选择适当的错误组织方式
  • 考虑错误的序列化和显示需求
  • 在测试中使用简化的错误处理,但保持与生产代码相似的风格

这种方法允许我们在项目开发的不同阶段灵活地处理错误,同时保持代码的一致性和可维护性。


20240808

Rust错误处理的四个层次

我叫Tim,让我们来谈谈错误处理,特别是如何随时间提高错误处理技术的成熟度,当你使用Rust编程语言时。这是我们会议的议程:

首先我们将定义错误实际上意味着什么,从你作为程序员的视角,以及类型系统的视角,错误是什么。我想花点时间谈谈Rust中的类型,以及结果通常包裹一些其他类型T,那实际上意味着什么。然后我想逐步通过多个错误定义的成熟度水平,以一种可重用的方式为你,也希望为你的用户。

但首先我想解释一个故事,一个关于我如何学习Rust的故事。我开始玩像这样的事情:

1
2
let mut out = std::io::stdout();
write!(out, "Hello, world!");

那还不错,我弄清楚了你需要导入一个特质,以便你能访问东西。但结果仍然有问题,我会得到这个编译错误:”你知道这返回一个结果,哪个可能是错误变体,应该处理”。我想,”好的,那似乎很奇怪。”

我做什么?我忽略了问题。立刻,我花了一个…我只是想看看,我可以让它消失,我可以纠正错误,走开,就像事情会打印出来,我的问候信息会通过。那就可以了。

然后我发现,实际上可以从main返回结果,借助问号操作符的帮助。这看起来与我之前的不太一样:

1
2
3
4
5
fn main() -> Result<(), std::io::Error> {
let mut out = std::io::stdout();
write!(out, "Hello, world!")?;
Ok(())
}

我可以说我可以让错误向上传递到main。我需要底部的这个奇怪的东西,不过,好吧,单元,呃,所以内括号或圆括号,在圆括号中是单元类型,这是结果的可接受变体。变体一词来自于Rust中的枚举有你可以是的变体。结果可以有两种,但结果实际上在标准库中有多种,这后来造成了一个问题。

所以如果我继续,当我想要开始学习时,这是一个更复杂的例子,所以我返回了不同的结果类型。在这种情况下,我实际上正在将我的问候信息写入文件Hello.txt:

1
2
3
4
fn main() -> Result<(), std::io::Error> {
std::fs::write("Hello.txt", "Hello, world!")?;
Ok(())
}

问号操作符做了一些不同的事情。它返回了一个std::io::Result<()>从write!宏和print返回,并从这些std::fs调用中,例如,从字符串中读写。那看起来还可以,一切都很顺利。

然后我遇到了另一个问题。我会尝试合并错误类型。如果你看这种类型,身体或本质上关注,我们真正想要的是能够创建某种返回类型,代表错误。因为问题是我们面临或我开始面临的是std::io::Result,不是std::fmt::Result。当我想要合并或复用错误类型时,一切开始变得非常复杂。

这是你的神奇出路,结果是,你可以使用特质对象。这是我的学习旅程,你的Box说,这是一个更复杂的错误类型,所以更复杂的返回类型。我需要,我仍然有我的单元在左边,但在右边,我也定义了一个错误。我不再以std前缀我的结果类型,I/O或std::fs,因为我实际上使用了一些隐式的东西,即std::result。这种结果类型是第三种结果,这个在标准结果模块中定义,默认包含在每一个Rust程序中。

我使用新的一个的原因是,在第三行下,或底部第三行,这个内容trim().parse()方法试图解释它正在读取的或字符串。它是,跟踪的parse方法试图解释一些字符串为数字并转换为,或转换为整数,并返回一个生成的错误。这是,我认为parse int错误,与文件系统无关。所以这,在第三行下返回的错误,是不同的,与从文件系统生成的错误。

所以我们得到的是一个系统,其中问号操作符将任何具体错误类型转换为某种动态特质对象。现在这非常非常有帮助,因为这意味着本质上你可以假装自定义错误类型不存在。对我来说,那就像一次思维的扭曲,当我学习语言时,从只是玩玩,陷入真正的错误和结果麻烦,以及其他一堆东西,并逐渐熟悉其他一些机制,这些机制真的可以让Rust感觉几乎像动态语言。

好的,我们刚刚绕了个弯,让我们回到议程。现在我们要尝试描述错误的两种方式。

首先我们需要程序员的视角。一个错误,现在程序可能会将错误视为不常发生的事,因此我们有我们的,我们可能会使用边缘情况或异常术语,其中错误是未计划的事。你知道,它可能会发生,但我们可能不会…我们也可以将一个错误视为一个标记,我们需要为调试提供线索。本质上是一个标记,表明问题已经发生,你应该实际提供一些有用的输出,以便你可以指示问题已经发生,供任何想稍后调查的人。最后,有一种观念是,一个错误可能是完全意想不到的,所以前两个我们可能会遇到,程序可能遇到完全未预料的状态,典型结果是整个程序崩溃,这在Rust的世界里称为恐慌。

类型系统,某种程度上,对Rust的类型系统有一个更简单的看法。Rust的类型系统特别对待错误,将其视为值。它们没有任何特殊之处。一个错误特征是实现特质,学习错误错误,这是标准库标记,错误模块内,该模块内有错误特质。结果所有实现显示的自动,将有一个错误定义实现,显示特质或开始错误。抱歉,我写了他们的开始错误显示,但实际上是,我相信,std::fmt::Display。很抱歉错误的模块,但显示特性是为所有类型实现的,我们期望终端用户能看到消息,如果这有意义。它与调试特性略有不同,后者可以自动推导,显示特性需要手动实现。这在某种程度上有优势。

如果我回到这里,我们实际上可以实现显示,然后自动使用。作为一种错误类型,稍后当我们想使用字符串时,这会派上用场。

现在我认为解释一下结果是什么可能会有用。结果实际上只是一个枚举。请允许我稍微更详细地充实一下。我将打开一个网络浏览器,我们将前往Rust的标准库。如果我转到Rust然后搜索结果,DuckDuckGo,将搜索结果。结果就是,实际上如果我查看源代码,然后搜索结果,哦,有一个模块可能想让我进去。我想我是否能找到它,这里,就是,所以,实际上是一个带有几个额外注释的枚举,但核心就是Ok和Err。这些额外注释对文档有用,实际上编译器,哦,这些lang项,让我们忽略那个,但本质上只是一个类型。

有趣的是它是,所以我们也有一些关于必须使用的额外注释,例如,这是错误消息出现的地方。有趣的是结果实现错误,抱歉不,那里有,是的,所以所以呃,有错误,哪个错误是什么,错误是,哪个确定哪个可以作为,可以打印到屏幕上。哦天哪,那是一个非常糟糕的解释,但希望你会和我在一起。

现在,你会注意到结果有两个东西,那两个泛型参数,T和E。你也会记得我进入了一个早期的文本例子,那里有标准I/O错误和标准格式错误,它们不同。到底发生了什么,结果发现对于单个模块来说,描述一种,定义自己的错误类型。原因是这变得如此,这又是,它们只是值,事实上,在这种情况下它们是结构体。但使它们有用的,是他们能做到,他们实现另一个特征。在这种情况下,他们实现显示,这意味着以迂回的方式,和这些单个模块中定义的错误类型,在这种情况下,std::fmt实现显示。有一个编译器知道如何对待任何实现显示的东西作为错误特征。

所以我们有点,我们在这里重载了名称。我认为这是最令人困惑的事情之一。结果分散在各个地方,如果我看,如果我在Rust文档中看,我会看到大约三十种不同的结果类型,不能以相同的方式使用。错误是一样的。这是Rust社区的一种惯例,实际上定义了两个本质上重载的名称。防止结巴,但我知道很多人可能来自Java,会喜欢命名空间不那么激进。

无论如何,那只是一个…现在顺便说一下,我在哪里,我想稍微多谈谈错误处理的成熟度,特别是,我们如何可以开始忽略结果。让我们把代码带上来,所以这是之前的一些代码,你可以看到,这里是我们的hello.txt,我正在转换,我直接返回这个对象。它恰好实现了这个错误特征,我之前描述过,这很好。我要注释,复制它,以便我稍后可以参考,但我要忽略所有这些,并尝试看看我通过本质上忽略一切能走多远。

忽略一切的传统方式是unwrap。现在unwrap是一个有点奇怪的话,关于错误。可以这样想,实际上,曾经有一阶段,顺便说一句,作为趣闻,提议将这称为断言,但如果考虑结果有两种变体,unwrap会,unwrap本质上说,取出内部内容,给我结果包含的。

好的,我们有一堆问题。哦,实际上我们正在返回结果,我将得到,开始。实际上这很好,这听起来像是一个非常奇怪的话,对于一个关心类型系统的人来说,但如果unwrap让你得到一个运行的程序,它实际上可能是一个你可以继续的方式。我不是说它在每个情况下都有用,但我说对于像一次性脚本之类的东西,它实际上可能是一件值得尝试的事情。

一件值得真正尝试的事情,你知道,我们可以做一些更好的事情。我们可以expect并提供错误消息,”无法创建文件”。你知道,这比这更进一步。同样的事情在这里,我们可以说,你知道,所有这些,你知道,也许文件自从我们,自从我们实际上尝试创建它以来,已经被删除了。

我可以更进一步,此时我将创建一个格式化消息。尽管,一旦你开始使用expect,实际上有些东西,也许你可以想到做的事,就是使用一些可用的错误功能。所以我要再制造一个问题,实际上我们已经走了很远,只是忽略错误。另一种方法是,那在这种情况下,第九行,我们也可以知道我们不在乎,你可以简单地分配给或绑定到输出。所以write!宏,例如,返回一个结果,返回一个单元。所以它是,这是一种稍微更干净的方式,去,因为unwrap感觉有点疯狂。它感觉危险,它感觉危险的原因是我们现在要看到的,因为在第十五行我们正在解包。事实上我会让它超级清楚,比如说我说hello,我们得到一个恐慌。事实上,它说,parse,内部类型,它是一个无效的数字。这是一个问题,你不希望你的用户遇到这样的事情。

所以我们的选择是什么,嗯,我们谈论过,期待,我们可以添加额外信息,嗯,我们可以,嗯,有一个备选方案,如果我们映射一个时代,我们可以,嗯,嗯,我们可以,所以,我们有一个暂停和错误,我就叫它paerror,我们本质上,可以翻译成其他东西,我们可以稍微将其转化为,我们可以有自己的时代类型,我们可以简单地称其为单位,实际上我们可以定义它,嗯 是的,我们暂时就拥有自己的错误类型,嗯 然后说,如果 好的 回答

然后,嗯,我想知道现在会发生什么,代码看起来相当丑陋,因为大小,我只是要删除这一个

然后,我想要一个else块,嗯,嗯,好的,所以现在我可以将move print answer放入else,嗯 现在我可以说 a print line,帮助,我想知道这个是否可行,可能不行,它说实际上不知道什么时代类型,这是因为我们在,在我们的代码中,嗯,没有定义一个时代,所以 我可能,嗯,实际上我们不能访问,哦,实际上,我们可以尝试,不,它不会对我们非常满意,因为我们实际上没有定义一个完整的时代类型,这有点疯狂,嗯,但由于我们没把时代带回来,类型推断机制无法,嗯,真正处理这种想法,将外部错误包裹并调整,嗯,所以,我想鼓励你,然而,去,嗯,尝试一些选项,我们这样做,随着成熟度增加,变得更成熟,你可以自己转换,但是我们就,我们想说我们的初级成熟度是能够,嗯,完全忽略时代,新系列九专业,毛凯升级,我放弃了,我可以放弃,每项工作,每项业务,当我们知道为什么时,工作感觉更好,音乐,个人工作和微笑,啊哈,所以现在是我们第二,嗯,我指的是什么,嗯,我的意思是,而不是真正有一个,嗯,错误类型,我们实际上可以使用字符串,或者如果你喜欢,你可以使用,嗯,如果你有固定消息,你可以返回,甚至,一个静态的,嗯,抱歉,绑定到静态生命周期的字符串切片,所以在这种情况下我想知道它是否仍然,现在我们有一大堆编译器错误,说问号运算符将不再工作,这,嗯,我在想我现在能否,做我之前想做的事,实际上是调整,所以这是一个,我只是要注释掉其他一些错误问题,我们正在尝试做的是,将任何其他错误转换为我们的,除了,再次类型推断,朋友不如我们想要的那样友好,嗯,那个,我将创建另一个函数,显示,一个字符串,所以这里有一些输入文本,这里有一个字符串切片,出于某种原因我想返回一个结果,该结果将只有好的或类似单位,我可以将其用作哨兵值,然后一些字符串的错误,假设出于某种原因,如果文本以问号开头和类似,嗯,如果文本以问号开头,那么出于某种原因它是,一个无效字符串,然后我可以返回,然后我可以返回一个字符串,无效,我可以返回好的,这里然后我将打印出,文本,嗯,这实际上是另一个,我在这里也会做所有这些,嗯,出于某种原因,我正在打印出路径,我正在显示我的路径,嗯,它返回一个结果,所以再次我想使用问号操作符,和我所做的一些有趣的事情是,因为我有两个结果类型,这里有一个字符串,这里有一个静态字符串,他们不知道我不能,嗯,类型系统不能自动知道如何将一个转换为另一个,所以我可以实际上重复类型,并希望,嗯,事情会解决,好的,我可以打印出路径,这有帮助,所以嗯,我之所以认为,实际上可以是一个相对有效的事情,嗯,定义一个包含,嗯,嗯,定义成人结果字符串是它可以,嗯 它可以让你,通过官僚程序,做我即将建议的事情会很繁琐,所以如果我有多重不同类型,嗯,嗯,呃,我哦,我有一些不需要的特征,我可以,嗯,实际上,那很有趣,我不实际做这件事有多久

嗯,实际上,我本来要说第二个没打印任何东西,那是因为,嗯,我实际上什么都没做,所以这是一个问题,Rust总是要求你实际使用输出,好的,那很有趣,期待某些东西,嗯,在这里,哦,我需要一个,好的,所以它是十四,十四字节字符,十四字符长,但我们真正谈论的是结果,嗯,你知道,嗯,如果我们只为自用,我们并未真正构建库,或仅在实验,我们可以使用,愚蠢的,呃,本质上可用任意字符串输出,呃,作为错误消息,嗯,但有一个大好处,但问题是,我们本质上忽略所有,呃,所有类型系统,回到我的代码,实际上定义自定义错误类型更好,它可以在代码上下文中定义,Rust约定是,呃,实际上可以复用错误术语,呃,并覆盖该术语,呃,即使,这有点烦人,如果你在决定不同类型的,呃,不同类型的错误,所以,现在我将返回错误类型,呃,假设我不能返回字符串,所以,但如果长度是,听,我有错误且太短,同样如果大于五十或一百,然后太长,否则我可以返回长度OK值,我有了某种验证步骤,为何不高兴,呃,说期望,啊,可能这个,现在我有,我有两种不同错误,这个不再返回,实际上,还是差不多,因为我们手动处理,我们没担心,关于那个时代类型,如果我回到,问号操作符,一旦我创建多个东西,编译器会给我问题,所以在我代码中可以两种,一是为每个可能错误定义时代,所以我的时代类型可能50行,否则可以稍微更细,根据是否有与用户输入相关,对比可能与文件系统权限相关,或本质上分组不同错误类型提供更多上下文,但会随时间变得难以控制,和,可能是个想法,但让我们继续,可能是个想法,花点思考,或一点精神能量,确定平衡点,因为很快会面临问题,我就说无效,我会,嗯,和,现在问号操作符会起作用,哦,如果我返回,嗯,我的时代类型,现在问号操作符应该高兴,因为两个函数实现相同,知道如何转换自己,比如我想说转换自己,他们不知道如何转换自己成字符串,这就是为什么创建时代,但一旦,那个时代,类型系统满意,嗯,我们可以,一旦类型系统满意,代码编译,你可能想知道,为什么费心所有这些,嗯,,定义自定义时代类型,实际并不划算,嗯,如果你只是写小脚本,嗯,但当你,定义一个,嗯,可以更好,更好,当你尝试有特定动作,我们知道有时会有错误,因此我们可以要求用户检查文件系统,但如果他们或我们想提供更具体的错误消息,如果他们,嗯,有某种数据损坏,嗯或输入错误,现在我有,我需要能够,实际上可以匹配所有变化,单独并做不同的事

嗯,太短,我们可能想要,嗯,不同的路径,比如说太短,我们可以实际上复制文本,而不是返回错误,我们可以,嗯,显示路径,我们可以创建其他字符串,像我们知道有,嗯,所以太短,我们想要做的是我们有临时字符串,容量说一百,然后,嗯,我们扩展它,嗯,增长,然后再做一次,哦,我从中得到了一堆丑陋的错误消息,现在我将实际打印temp再次,如果这次这不起作用,它只是会放弃,嗯,同样,如果太长,我可以做点别的,即我可以尝试显示,嗯,路径,但仅显示前约20个字符,比如说,和,我就,嗯,我可以,哦,这是什么,嗯,我仍然得到另一个,另一个关于未来错误的警告,这是因为第二个对display的调用是从我的文本中处理的,它们是什么,嗯,它们实际上生成自己的错误,现在它们有自己的结果,结果是我仍然有恐慌,这不是我想要的,嗯,为了,哦,因为假设少于两个,好的,好的,嗯,最后,嗯,显然编译器知道,嗯,关于如何做这件事,我得到,嗯,那很好,我应该停止玩

嗯,你如此的原因,你可能在问的问题是,为什么在地球上,你会费心定义一个枚举,当你实际上可以匹配字符串,原因在于你可以提供这些穷尽性检查,如果你只是返回字符串作为错误,就像我们最初做的那样,然后在你的匹配语句中,试图本质上,嗯,确定实际错误是什么,你最终会创建这些微型暂停,这可能会变得非常脆弱,好的,哦,嗯,让我们回到幻灯片,我们实际上可以做的最后一件事是使用奇怪的,涉及大量官僚程序,嗯,与那一起,让一切都准备好,嗯,结果有一种更简单的方法,嗯,特别是你可以扩散这个叫做这个时代的箱子,它将处理,与实际定义自定义枚举类型相关的许多官僚程序,嗯,我将向您展示关于文档的非常简短的提示,只是为了让您感受一下我的意思,稍等,所以这里是,网站,你可以看到我有,嗯,这是一个例子,我拉入我的这个时代时代类型,这是我的枚举,我得到,我可以有自定义错误消息并说实际上这是从另一个,另一种类型的错误,所以我可以返回数据存储错误并给出断开,枚举,如果它是由,并且它最初来自标准的,Io时代,嗯,可能有一些原因需要编辑值,可能我们试图隐藏秘密,嗯,可能有一些数据我们不再想要返回,因此,这是一个很好的示范,能够实际提供格式字符串,嗯,同样,并且它为开始事情提供了很多简单性,但现在结果,尽管那不是你可以采取的唯一方法,你实际上可以返回这些特征对象,我想向您指出的两个箱子是,指向你,有一个名为anyhow的箱子,它也是,它基本上与,嗯,只是自己定义特征对象,嗯,那么耳罩箱子在简·爱之后,箱子的创作者,嗯,拼写有点不同,嗯,但相当…,相当整洁,它提供你的代码或能力找到报告,您有详细的错误报告作为其中一部分,因此我强烈建议您也玩玩那个,我们快结束了,我想向您指出一些额外资源,我希望你喜欢直播或录音,取决于您跟随哪一个,嗯,还有很多关于这个话题,从这里开始的最佳方式是真正尝试,因此我鼓励您这样做,希望您有一些资源,嗯,嗯,一些技巧和窍门,实际上,我现在真正想从您那里得到的是信心说,学习是可以的,使用字符串是可以的,如果您需要,实际上,从小开始就很好,您会发现自己会逐渐寻求提供更多健壮性的工具,然而,现在如果您是学习者,重点真的需要放在保持认知低上,我希望向您展示您可以使用字符串,,您甚至可以忽略错误的可能性,实际上是一种有效的路径,或实际上在某些情况下是有效的路径,如果您正在编写小型实用程序和为自己编写东西,并且您正在学习Rust,尝试避免,嗯,尝试避免一次性学习所有内容,给自己时间放入待办事项,我们需要为这个做适当的错误,嗯,就这样,我将离开您,嗯,,我希望您喜欢这里的课程,好的,干杯,再见,再见