Rust 中的错误处理 已翻译 100%

xiaoaiwhc1 投递于 04/26 10:46 (共 76 段, 翻译完成于 05-08)
阅读 3140
收藏 30
3
加载中

就像大多数编程语言一样,Rust 让程序员使用一种特定方式来处理错误。一般来说,错误处理分为两类途径:异常和返回值。Rust 使用的是返回值。

在本文,我打算为 Rust 中如何处理错误做一个全面论述。不仅如此,我会尽量每次只引入一种错误处理方式,那么在如何将每一种组合到一起的时候,你就可以有一个坚实的实践知识基础。

如果使用最幼稚的方法,Rust 中的错误处理可以是冗长且恼人的。本文会探索那些绊脚石,并且示范如何使用标准库来让错误处理更加简洁且符合习惯。

琪花亿草
琪花亿草
翻译于 04/26 16:12
0

目标受众:那些不熟悉Rust错误处理惯用法的Rust新手。对Rust有一定了解是很有帮助的。(本文大量使用了一些标准特性和一些非常少量的闭包和宏。)

更新(2018/04/14):示例已转换为?,并添加了一些文本以提供有关改动的历史背景。

简要说明

本文中的所有代码示例都使用Rust 1.0.0-beta.5进行编译。当Rust 1.0稳定版发布以后,他们应该能够继续工作。

Tocy
Tocy
翻译于 04/26 15:59
0

我的博客的代码仓库中,所有代码都是可见的而且可以编译。

Rust 手册有一个章节讲错误处理。它提供了一个非常简洁的概要,但是(还)没有足够深入讲解细节,特别是使用最近添加的标准库时。

运行代码!

如果你需要运行下面代码示例中的代码时,可以使用下面的方法运行:

$ git clone git://github.com/BurntSushi/blog
$ cd blog/code/rust-error-handling
$ cargo run --bin NAME-OF-CODE-SAMPLE [ args ... ]

每个代码示例都是以它的名称为标签。(没有名字的代码示例不能使用这种方法运行。抱歉。)

琪花亿草
琪花亿草
翻译于 04/26 16:19
0

内容概要

这篇文章非常长,主要是因为我是从 sum 类型和连接器开始讲起,然后逐渐一点一点地告诉你 Rust 是如何进行错误处理的。因此,一些有其它编程语言的类型系统的知识的程序员就可以跳过简单的部分。下面是一个简短的学习指导:

Rust 是一个系统级编程语言并且有一个具有丰富表达能力的类型系统(它的目标是C/C++的替代品)。如果你刚刚接触 Rust 语言,建议你从头一步一步读完这篇文章。如果你还完全没有听说过 Rust,建议百度或读官方文档 Rust book

如果你之前没有用过 Rust 但是有一些函数式编程语言经验(比如对"代数数据类型"、"连接器"等概念比较熟悉),那么你可以跳过基础部分,直接从“复合错误类型”开始,简单熟悉一下然后通读标准库的"Error Traits"(错误特性,对错误的一种约束,类似接口)。你也可能需要查询 Rust Book 以了解 Rust 的闭包和宏的概念(Rust Book 是官方学习 Rust 的经典资源)。

如果你对 Rust 非常熟悉了解,只想学习一下如何进行错误处理,你可以直接跳到最后面看那些练习题

  • 基础知识

    • 如何解析整数

    • Result 类型别名

    • Option<T> 类型的值的组成

    • 什么是 Unwrapping(复合类型展开)

    • Option 类型

    • Result 类型

    • 小插曲:unwrapping 不一定就是魔鬼

  • 如何组合使用错误类型(Multiple Error Types)

    • 组合 Option 和 Result 类型

    • 连接器的限制

    • 早期返回

    • try! 宏 和 ? 操作符(作者自己的模拟实现)

    • 自定义错误类型

  • 标准库中一些用来进行错误处理的特性(Traits)

    • Error 特性

    • From 特性

    • 真正的 try! 宏 和 ? 操作符(标准库实现)

    • 组合自定义错误类型

    • 对库作者的一些建议

  • 练习题: 如何读取人口数据

    • GitHub 代码

    • 初始化步骤

    • 参数解析

    • 逻辑编写

    • 用 Box<Error> 进行错误处理

    • 从 stdin 读入数据

    • 用自定义类型进行错误处理

    • 增加其它功能

  • 总结

xiaoaiwhc1
xiaoaiwhc1
翻译于 04/26 22:43
0

基础

我将错误处理看做是使用“值分配”来决定一个计算的成功与否。就像我们即将看到的,人工错误处理的关键就是减少大量的显式实例分解,这些是在保留代码可组合性的同时程序不得不做的。

保留代码可组合性是很重要的,因为如果没有这个条件,不管什么时候我们遇到意外都会遇上 panic。(panic 会导致当前任务的放弃,而且在大多数情况下,整个程序都会终止。)例子如下:

panic-simple

// 从1到10猜一个数。
// 如果与我想的数是一样的,返回 true,否则返回 false。
fn guess(n: i32) -> bool {
    if n < 1 || n > 10 {
        panic!("Invalid number: {}", n);
    }
    n == 5
}

fn main() {
    guess(11);
}

如果你愿意的话,运行这段代码很简单。

琪花亿草
琪花亿草
翻译于 05/04 14:48
0

如果你尝试运行这段代码,程序将会崩溃并显示如下所示的信息:

thread '<main>' panicked at 'Invalid number: 11', src/bin/panic-simple.rs:5

另一个更实际的例子。程序接受一个整型数作为参数,将其乘以 2 并打印输出。

unwrap-double

use std::env;

fn main() {
    let mut argv = env::args();
    let arg: String = argv.nth(1).unwrap(); // error 1
    let n: i32 = arg.parse().unwrap(); // error 2
    println!("{}", 2 * n);
}

// $ cargo run --bin unwrap-double 5
// 10

如果你给程序一个 0 作为参数(error 1)或者如果第一个参数不是一个整形数(error 2),这个程序就像第一个例子一样处于 panic 的状态。

我更倾向于将这种错误处理方式看做公牛穿过瓷器店。公牛将会到它想去的地方,但是它将践踏途径的一切。

kevinlinkai
kevinlinkai
翻译于 04/26 21:34
0

复合类型展开(Unwrapping)的解释

在之前的例子中(unwrap-double),我声明了如果达成了两个错误中的一个,程序将会出现简单的panic,程序不像第一个例子(panic-simple)那样直接调用 panic。因为 panic 嵌入在 unwrap 的调用中了。

在 Rust 中“unwrap”是指,“给我计算的结果,如果有错,就 painc 并且停止程序。”如果我只是展示代码的解包会更好,因为它非常简单,但是那样做的话,我们首先需要了解 Option 和 Result 类型。这些类型都有一个叫做 unwrap 的方法。

kevinlinkai
kevinlinkai
翻译于 04/26 22:02
0

Option类型

Option类型是在标准库中定义的:

option-def

enum Option<T> {
    None,
    Some(T),
}

Option类型是一种使用Rust的类型系统来表达“无”的可能性的方法。将“无”的可能性编入类型系统是一个重要的概念,因为它会导致编译器强制程序员处理“无”的情况。我们来看看一个试图在字符串中查找字符的例子:

Tocy
Tocy
翻译于 04/26 16:04
0

option-ex-string-find

// Searches `haystack` for the Unicode character `needle`. If one is found, the

// byte offset of the character is returned. Otherwise, `None` is returned.

fn find(haystack: &str, needle: char) -> Option<usize> {
    for (offset, c) in haystack.char_indices() {
        if c == needle {
            return Some(offset);
        }
    }
    None
}

(专业提示:不要使用此代码,应该使用标准库中的 find 方法)

注意:当这个函数找到一个匹配的字符时,它返回的不是一个 offset,相反,它会返回 Some(offset)。Some 是一个 Option 类型的变体或者数值构造函数。你可以认为它是一个函数,类型是 fn<T>(value: T) -> Option<T>。相应的,None 也是一个数值构造函数,除非它没有参数。你可以认为 None 是一个具有 fn<T>() -> Option<T> 类型的函数。

这看起来可能不值一提,但这只是故事的一半。另一外使用我们写的 find 方法。然我们试着使用它来查找文件名中的扩展名。

kevinlinkai
kevinlinkai
翻译于 04/26 16:18
1

option-ex-string-find

fn main_find() {
    let file_name = "foobar.rs";
    match find(file_name, '.') {
        None => println!("No file extension found."),
        Some(i) => println!("File extension: {}", &file_name[i+1..]),
    }
}

这段代码在 find 方法返回的 Option<usize> 中,使用了模式匹配来做 case analysis。事实上,case analysis 是获取存储在 Option<T> 内的值的唯一方法。这意味着,案例中当 Option<T> 是 None 而不是 Some(t) 的时候,作为程序员的你必须处理。

但等等,使用 unwrap-double 方法来 unwrap 会怎么样呢?这样就没有 case analysis!相反,case analysis 被放进了 unwrap 方法里,当你想用的时候可以自定义:

kevinlinkai
kevinlinkai
翻译于 04/26 16:30
0
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(0)

返回顶部
顶部