【译】Rust 的 Result 类型入门

   2023-03-08 学习力0
核心提示:A Primer on Rust’s Result Type 译文原文链接:https://medium.com/@JoeKreydt/a-primer-on-rusts-result-type-66363cf18e6a原文作者:Joe Kreydt译文出处:https://github.com/suhanyujie/article-transfer-rs译者:suhanyujietips:水平有限,翻译不当之
  • A Primer on Rust’s Result Type 译文
  • 【译】Rust 的 Result 类型入门

Result 类型是 Rust 中处理错误的常用方法类型,它比较灵活;应该是非常灵活!

对于那些正在学 Rust 的人来讲,Result 可能不太直观,你可以通过阅读它的标准库文档来了解如何使用是个不错的方法。如果你想迫切的学会它,也是可以的,但如果你只是用它处理错误或者使用某个返回 Result 类型的函数(很多人都这样做),你可能体会不到它的妙处。

为了节省大家的时间,我打算使用英语来解释 Rust 的 Result 类型。

Result 是什么?

Rust 权威指南

“Result 表达的是错误的可能性。通常错误是用来解释某种任务执行失败的原因。”

【译】Rust 的 Result 类型入门

用朴素的英语解释

Result 是一个函数返回的类型,它可以是 Ok,也可以是 Err。如果是 Ok,则表示函数按照预期执行完成。如果是 Err,则该函数出现了错误。

Result 用来做什么?

根据 Rust 权威指南

Result 类型是对计算过程中可能出现的结果的表示方式。按照惯例,如果一个结果是预期的 Ok,那么另一个结果则是意料之外的,即 Err

请再直观点

函数返回了值。这些值具有特定的数据类型。函数可以返回 Result 类型的结果。Result 类型根据函数是否按预期执行而变化。然后,程序员可以编写一些代码,如果函数按预期执行则返回 A,如果遇到异常,则返回 B。

不处理 Result,则产生异常

error[E0308]: mismatched types
  --> main.rs:20:26
   |
20 |     let my_number: f64 = my_string.trim().parse(); //.unwrap();
   |                          ^^^^^^^^^^^^^^^^^^^^^^^^ expected f64, found enum `std::result::Result`
   |
   = note: expected type `f64`
              found type `std::result::Result<_, _>`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0308`.
compiler exit status 1

报错信息中关键的部分是,“expected f64, found enum.”,类似的场景中,可能还会有:

- “expected u32, found enum”
- “expected String, found enum”
- “expected [insert type here], found enum”

如果你得到一个类似上面的错误,那是因为需要你处理函数返回的 Result 类型数据

类型为 Error 的 Result 的程序

use std::io::{stdin, self, Write};
fn main(){
    let mut my_string = String::new();
    print!(“Enter a number: “);
    io::stdout().flush().unwrap();
    stdin().read_line(&mut my_string)
        .expect(“Did not enter a correct string”);
    let my_number: f64 = my_string.trim().parse();
    println!(“Yay! You entered a number. It was {:?}”, my_num);
}

在这个程序中,它提示用户输入一个数字。然后将输入作为字符串读入并存储下来。我们想要的是一个数值类型,不是 String,所以我们需要使用 parse() 函数将其转换为一个 64 位浮点数(f64)。

如果用户输入的是一个数字,那么 parse() 函数将其转换为 f64 没什么大问题。但我们仍然会得到一个错误。

发生错误是因为 parse() 函数不只是将 String 转换为数字并返回。相反,它接受字符串,将其转换为数字,然后返回 Result 类型。Result 类型需要被解包才能得到我们需要的数值。

用 Unwrap() 或 Expect() 修复错误

转换后的数字可以可以通过在 parse() 后面附加调用 unwrap() 函数将数字从 Result 中“解包”出来,类似于这样:

let my_number: f64 = my_string.trim().parse().unwrap();

unwrap() 函数可以看出 Result 中类型,可能是 Ok,也可能是 Err。如果 Result 中包裹的类型是 Ok,那么 unwrap() 则返回它的值。如果 Result 中的类型是 Errunwrap() 则会让程序崩溃。

【译】Rust 的 Result 类型入门

你也可以用 expect() 函数像下方这样来处理 Result:

let my_number: f64 = my_string.trim().parse().expect(“Parse failed”);

expect() 的工作方式类似于 unwrap(),假如 Result 是 Errexpect() 将会使程序崩溃并且将其中的字符串内容 —— “Parse failed.”展示在标准输出中。

使用 unwrap() 和 expect() 的缺点

当我们使用 unwrap()expect() 函数时,如果遇到错误,程序会发生崩溃。如果错误发生的几率非常小,这也许可以容忍,但在某些情况下,错误发生的概率会比较大。

在上面的示例中,用户可能输入错误,输入的不是数值(可能是字母或者特殊符号)。我们并不想每次用户输入错误的内容程序就发生崩溃。相反,我们应该提示用户应该输入数字。这种场景下,Result 就非常有用,尤其是当它与一个模式匹配的表达式相结合的时候。

用匹配表达式修复错误

use std::io::{stdin, self, Write};
fn main(){
    let mut my_string = String::new();
    print!(“Enter a number: “);
    io::stdout().flush().unwrap();
    let my_num = loop {
        my_string.clear();
        stdin().read_line(&mut my_string)
            .expect(“Did not enter a correct string”);
        match my_string.trim().parse::<f64>() {
            Ok(_s) => break _s,
            Err(_err) => println!(“Try again. Enter a number.”)
        }
    };
    println!(“You entered {:?}”, my_num);
}

如果你问我怎么实现,上面就是示例代码!

前面提到的不优雅的实现和优雅的实现方式的不同点是在循环体内部。我们可以分解一下。

代码分析

在 loop 之前,我们提示用户输入一个数字。接着我们声明 my_num。

我们将循环体中返回的值(用户的输入,它将从字符串转换为数字)赋给 my_num:

let my_num = loop {

在循环体中,我们阻塞等待用户输入。然后接收用户的输入,在这个过程中我们有三个问题要解决。

  • 1.我们需要确定用户输入的是数字而非其他的字符,一个词或者一个字母。
  • 2.Rust 中的 read_line() 函数能够以字符串的类型拿到用户的输入。我们需要将其转换为浮点数。
  • 3.如果用户没有输入数字,我们需要清理变量,并提示和等待用户再次输入。

在第三部分问题(清理 my_string 变量)在循环体内的第一行就已经实现了:

my_string.clear();

下一步,我们接收用户的输入:

stdin().read_line(&mut my_string)
    .expect(“Did not enter a correct string”);

read_line() 函数返回一个 Result 类型。我们使用 expect() 函数处理它。在这种情形下是完全没问题的,因为 read_line() 出错的几率非常小。用户通常只能在终端输入一个字符串,而这正是 read_line() 所需要处理的。

The string of user input returned by read_line() is stored in the my_string variable.

通过 read_line() 把用户输入的字符串返回并存在 my_string 变量中。

The Juicy Part

渐入佳境

现在我们已经将输入的字符串存在 my_string 中,我们需要将其转换为浮点数。使用 parse() 函数可以实现,然后将浮点数结果返回。所以我们有不止 Result 的类型需要处理,但这一次,我们很可能会出现一个错误。如果用户输入的是非数字, parse() 将会返回一个错误类型的 Result(Err)。如果发生这种情况,我们不希望程序崩溃。而是希望提示用户没有输入正确的数字,请再试一次。为此,我们需要写好调用 parse() 成功时的逻辑,还要写好调用失败时的逻辑。类似于逐个处理匹配表达式可能的结果。

分析匹配表达式

match my_string.trim().parse::<f64>() {
    Ok(_s) => break _s,
    Err(_err) => println!(“Try again. Enter a number.”)
}

首先,我们使用 match 关键字来声明匹配表达式。然后,我们提供与表达式匹配的可能的值。这个值就是下面所示:

my_string.trim().parse::<f64>()

这段代码接收 my_string 参数,它将用户输入的内容保存下来,并提供给 trim() 函数。trim() 函数会删除掉字符串两侧可能存在的额外空行或空格。我们之所以需要 trim() 是因为 read_line() 函数在输入中附加了一个额外的空行,这会导致转换会出现异常。然后将清理了空格字符的 my_string 传递到 parse() 函数中,该函数会尝试将其转换为浮点数。

如果 parse() 成功地将 my_string 转换为数字,则返回 Ok。在这个情况下,我们可以得到浮点数。如果用户输入的不是数字,那么 parse() 将无法正常完成转换,它会返回 Err。

在匹配表达式的花括号(主体)中,我们根据 parse() 返回的类型告诉计算机怎么做:

Ok(_s) => break _s,
Err(_err) => println!(“Try again. Enter a number.”)

如果结果是 Ok,则表示 parse() 能够转换该类型。这时,我们调用一个 break,停止循环,并返回存储在 Ok 中的值,这个值会被放在 _s 变量中。

如果结果是 Errparse() 无法完成转换。这时,我们会告诉用户“重试一次。输入一个数字”。由于我们不调用 break,所以循环重新开始。

如果必须用一句话解释 Result,那就是:如果一个函数返回 Result,一个匹配表达式可以根据结果是 Ok 还是 Err 来执行不同的代码。

在你的函数中使用 Result

既然你已经了解了处理 Result 的方法,那么你可能会希望在你自己创建的函数中使用它。

我们先看一个例子。

fn main(){
    let my_num = 50;
    
    fn is_it_fifty(num: u32) -> Result<u32, &’static str> {
        let error = “It didn’t work”;
        if num == 50 {
            Ok(num)
        } else {
            Err(error)
        }
    }
    match is_it_fifty(my_num) {
        Ok(_v) => println!(“Good! my_num is 50”),
        Err(_e) => println!(“Error. my_num is {:?}”, my_num)
    }
}

这个程序检查 my_num 的值。如果值为 50,则表示成功;如果不是,则表示错误。

这段代码的主体是 is_it_fifty() 函数。它是有返回结果的声明式函数。我们逐行看其中的代码。

首先,我们声明 my_num 并给它赋值。然后,我们声明 is_it_fifty() 函数:

fn is_it_fifty(num: u32) -> Result<u32, &’static str> {

在我们的声明中,我们指定该函数接收一个名为 num 的参数,其类型是 32 位无符号整数类型(u32)。接下来,我们指定函数的返回值类型。表示函数会返回一个结果,类型是 u32 或字串(&'static str)

然后,我们编写 is_it_fifty() 的函数体。

let error = “It didn’t work”;
if num == 50 {
    Ok(num)
} else {
    Err(error)
}

函数体中的代码是一个 if else 表达式。它用于判断传入的参数。

如果值是 50,那么函数将返回 Ok 的 Result。Ok 中将会包含传递给函数的值(num)。

如果参数不是 50,函数将返回 Err 的 Result。Err 会包含错误变量的值,也即 “It didn’t work.”

无论何时使用该函数,都必须处理它返回的 Result。在我们的程序中,与大多数 Rust 程序一样,是通过一个匹配表达式完成的。我在之前已经描述过部分匹配表达式。

Result 类型可以使用 unwrap()expect() 来处理 —— 前面也已经解释过。

【译】Rust 的 Result 类型入门

总结

Result 是一个函数的返回类型,它表示函数执行是否成功。

Rust 的许多内置函数都是返回 Result 类型,如果是这样的话,就没有办法避开它。如果一个函数返回 Result,它必须要被妥善处理。

处理 Result 常用的方法是使用 unwrap() 和 _expect() 函数以及匹配表达式。

可以从自己定义的函数中返回 Result。这是处理错误的好办法。

关于 Rust 的 Result 类型,你需要知道的就这些了,但是如果想了解更多信息,或者想知道我从哪儿收集的这些信息,可以参考下方的资源列表。

资源

 
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • bloom-server 基于 rust 编写的 rest api cache 中间件
    bloom-server 基于 rust 编写的 rest api cache
    bloom-server 基于 rust 编写的 rest api cache 中间件,他位于lb 与api worker 之间,使用redis 作为缓存内容存储, 我们需要做的就是配置proxy,同时他使用基于share 的概念,进行cache 的分布存储,包含了请求端口(proxy,访问数据) 以及cache 控制端口(
    03-08
  • #新闻拍一拍# Oracle 调研如何避免让 Java 开发者投奔 Rust 和 Kotlin | Linux 中国
    #新闻拍一拍# Oracle 调研如何避免让 Java 开发
     导读:• 英特尔对迟迟不被 Linux 主线接受的 SGX Enclave 进行了第 38 次修订 • ARM 支持开源的 Panfrost Gallium3D 驱动本文字数:977,阅读时长大约:1分钟作者:硬核老王Oracle 调研如何避免让 Java 开发者投奔 Rust 和 KotlinOracle 委托分析公司 Omd
    03-08
  • Linux系统下Rust快速安装:国内镜像加速
    Linux系统下Rust快速安装:国内镜像加速
    官方网址和方法Install Rust - Rust Programming Language然而速度慢得让人难以置信。利用国内镜像进行windows的Linux子系统的Rust安装。rust 使用国内镜像,快速安装方法参考:RUST安装慢怎么办,使用镜像方式安装_网络_为中华之崛起而编程-CSDN博客我的操作
    03-08
  • Rust到底值不值得学--Rust对比、特色和理念
    前言其实我一直弄不明白一点,那就是计算机技术的发展,是让这个世界变得简单了,还是变得更复杂了。当然这只是一个玩笑,可别把这个问题当真。然而对于IT从业者来说,这可不是一个玩笑。几乎每一次的技术发展,都让这个生态变得更为复杂。“英年早秃”已经成
    03-08
  • 超33000行新代码,为Linux内核添加Rust支持的补丁已准备就绪
    超33000行新代码,为Linux内核添加Rust支持的补
    https://mp.weixin.qq.com/s/oKw9aBJSdmRoO6-rbLAkNw7 月 4 日,一套修订后的补丁被提交至 Linux 内核的邮件列表中,该补丁为在 Linux 内核中以 Rust 作为辅助编程语言提供了支持,借助 Rust 可以提高 Linux 内核和内存的安全。整套补丁包含 17 个子项,不光
    03-08
  • Rust实战系列-基本语法
    Rust实战系列-基本语法
    主要介绍 Rust 的语法、基本类型和数据结构,通过实现一个简单版 grep 命令行工具,来理解 Rust 独有的特性。本文是《Rust in action》学习总结系列的第二部分,更多内容请看已发布文章:一、Rust实战系列-Rust介绍“主要介绍 Rust 的语法、基本类型和数据结
    03-08
  • 全栈程序员的新玩具Rust(三)板条箱
    上次用到了stdout,这次我们来写一个更复杂一点的游戏rust的标准库叫做std,默认就会引入。这次我们要用到一个随机数函数,而随机数比较尴尬的一点是这玩意不在标准库中,我们要额外依赖一个库。很多编程方案都有自己的模块化库系统,rust也不例外,不过rust
    02-10
  • 全栈程序员的新玩具Rust(六)第一个WASM程序
    全栈程序员的新玩具Rust(六)第一个WASM程序
    先上代码https://gitee.com/lightsever/rust_study/tree/master/wasm_hello01webassembly就不用再赘述了,耳朵里面快磨出茧子来了。rustwasm是火狐自家的玩具,让我们来继续做实验,让rust飞起来吧。环境安装安装好rust环境之后仍然需要 一个 wasm 工具包carg
    02-10
  • 【Rust】标准库-Result rust数据库
    环境Rust 1.56.1VSCode 1.61.2概念参考:https://doc.rust-lang.org/stable/rust-by-example/std/result.html示例main.rsmod checked {#[derive(Debug)]pub enum MathError {DivisionByZero,NonPositiveLogarithm,NegativeSquareRoot,}pub type MathResult =
    02-09
  • unity rust_移植到Unity 5 –不为人知的Rust之旅
    unity rust_移植到Unity 5 –不为人知的Rust之
    unity rust Facepunch Studio的创始人Garry Newman和他的团队一直在将Rust移植到Unity 5。 这篇博客文章讲述了尚未结束的《Rust之旅》。 你敢和我们一起旅行吗? (Facepunch Studio’s founder Garry Newman and his team have been porting Rust over to Uni
    02-09
点击排行