Rust中文翻译21

4.7 错误处理

有时候程序会发生错误.对于不可避免的事情发生时最好有一个计划来处理.Rust有丰富的处理错误的方法.

你的程序会出现两种类型的错误:失败和崩溃.我们先讨论两者的区别,然后讨论如何处理他们.然后,我们讨论把错误升级为崩溃.

Page 107

4.7.1 失败和崩溃

Rust使用两种错误的类型:失败和崩溃.失败是一种可以被挽回的错误.崩溃却不行.

"挽回"是什么意思呢?在多数情况下,很有可能会发生错误.例如,一个parse函数:

"5".parse();

这个方法将一个字符转换为另一个类型.但是因为他是字符,你不能保证这个转换可以正常工作.例如,它该被转换成什么类型呢?

"hello5world".parse();

这个就不能工作.所以我们知道这个函数只能在某些输入下才能工作.这是可以预期的行为.我们称这种错误是失败(failure).

另一方面,有时候,我们会遇到不可预期的错误,或者我们没法挽回的错误.一个经典的assert!的例子:

assert!(x == 5);

我们使用assert!来声明一个表达式是true.如果不是,那么就有错误发生.这个错误导致我们无法继续当前的状态.另一个例子是使用unreachable!()宏:

enum Event {

NewRelease,

}

fn probability(_: &Event) -> f64 {

// real implementation would be more complex, of course

0.95

}

fn descriptive_probability(event: Event) -> &‘static str {

match probability(&event) {

1.00 => "certain",

0.00 => "impossible",

0.00 ... 0.25 => "very unlikely",

0.25 ... 0.50 => "unlikely",

0.50 ... 0.75 => "likely",

0.75 ... 1.00 => "very likely",

}

}

fn main() {

std::io::println!(descriptive_probability(NewRelease));

}

我们得到了一个错误:

虽然我们知道我们覆盖了所有的可能,但是Rust不能.他不知道可能区间是从0.0到1.0.所以我们需要加一个case:

use Event::NewRelease;

enum Event {

NewRelease,

}

fn probability(_: &Event) -> f64 {

// real implementation would be more complex, of course

0.95

}

fn descriptive_probability(event: Event) -> &‘static str {

match probability(&event) {

1.00 => "certain",

0.00 => "impossible",

0.00 ... 0.25 => "very unlikely",

0.25 ... 0.50 => "unlikely",

0.50 ... 0.75 => "likely",

0.75 ... 1.00 => "very likely",

_ => unreachable!()

}

}

fn main() {

println!("{}", descriptive_probability(NewRelease));

}

我们不必一直增加这个_case,所以我们使用unreachable!()宏来指出这一点.它是另一种错误处理的方式.Rust称这种错误为panic.

4.7.2 使用Option和Result来处理错误

一个最简单的处理函数失败的方法就是使用Option<T>类型.例如,find方法在字符串中试图找到一个匹配的串,然后返回一个Option:

let s = "foo";

assert!(s.find(‘f‘), Some(0));

assert!(s.find(‘z‘), None);

这基本就是最简单的方法了,但是这样的话我们没法得知错误信息.如果我们想要知道为什么失败了呢?为此,我们需要使用Result<T,E>类型.像这样:

enum Result<T, E> {

Ok(T),

Err(E)

}

这个枚举有Rust语言自身提供了,所以你不必定义它.Ok(T)变量代表成功,Err(E)变量代表失败.返回一个Result类型而不是Option类型是推荐的方法,除非你不关心失败信息.

有一个列子:

#[derive(Debug)]

enum Version { Version1, Version2 }

#[derive(Debug)]

enum ParseError { InvalidHeaderLength, InvalidVersion }

fn parse_version(header: &[u8]) -> Result<Version, ParseError> {

if header.len() < 1 {

return Err(ParseError::InvalidHeaderLength);

}

match header[0] {

1 => Ok(Version::Version1),

2 => Ok(Version::Version2),

_ => Err(ParseError::InvalidVersion)

}

}

let version = parse_version(&[1, 2, 3, 4]);

match version {

Ok(v) => {

println!("working with version: {:?}", v);

}

Err(e) => {

println!("error parsing header: {:?}", e);

}

}

这个函数使用了一个枚举, ParseError, 来枚举不同的错误.Debug特性可以是我们通过{:?}格式操作符来打印枚举值.

4.7.3 panic!不可恢复错误

当非预期的或不可恢复的错误发生时,panic!宏触发一个崩溃.它会让当前线程崩溃然后给出一个错误:

panic!("boom");

输出

thread ‘<main>‘ panicked at ‘boom‘, hello.rs:2

因为这种情况很少发生,所以谨慎的使用它.

4.7.4 将失败升级为崩溃

在某些情况下,甚至当一个函数失败的时候,我们希望把它当作已给崩溃而不是失败来处理.例如,io::stdin().read_line(&mut buffer)返回了一个Result<usize>,当读取一行输入失败的时候发生.这是我们可以处理这个失败并且恢复它.

如果我们不想处理这个错误,我们就想要终止程序运行,我们可以试用unwrap()方法.

io::stdin().read_line(&mut buffer).unwrap();

当Result是一个Err时,unwrap()会panic!.这意味着"给我值,如果那里出错了,让程序崩溃."这比起尝试恢复程序来说有点不靠谱,但是却更简洁.有时候,崩溃就对了.

另一种方法比unwrap()要好一点儿:

let mut buffer = String::new();

let input = io::stdin().read_line(&mut buffer)

.ok()

.expect("Failed to read line");

ok()把Result转化为Option,expect()对unwrap()做了同样的事,但是包含一条信息.这条信息传给了潜在的panic!,它提供了一个更好的带有错误信息的错误.

Page 111

4.7.5 使用try!

当调用多个函数都返回Result类型的时候,错误处理写起来会很繁琐.使用try!宏可以隐藏那些在调用栈上弥漫开来的错误信息.

把下述代码:

use std::fs::File;

use std::io;

use std::io::prelude::*;

struct Info {

name: String,

age: i32,

rating: i32,

}

fn write_info(info: &Info) -> io::Result<()> {

let mut file = File::create("my_best_friends.txt").unwrap();

if let Err(e) = writeln!(&mut file, "name: {}", info.name) {

return Err(e)

}

if let Err(e) = writeln!(&mut file, "age: {}", info.age) {

return Err(e)

}

if let Err(e) = writeln!(&mut file, "rating: {}", info.rating) {

return Err(e)

}

return Ok(());

}

替换为:

use std::fs::File;

use std::io;

use std::io::prelude::*;

struct Info {

name: String,

age: i32,

rating: i32,

}

fn write_info(info: &Info) -> io::Result<()> {

let mut file = try!(File::create("my_best_friends.txt"));

try!(writeln!(&mut file, "name: {}", info.name));

try!(writeln!(&mut file, "age: {}", info.age));

try!(writeln!(&mut file, "rating: {}", info.rating));

return Ok(());

}

将代码包裹在try!中会导致成功值,除非Result是Err,函数将会提前返回.

需要注意的是你只能在一个函数返回Result的时候使用try!,也就是说你不能在main()函数中使用try!,因为main()函数不返回任何值.

try!使用了From<Error>来决定返回什么错误.

时间: 2024-11-02 09:46:53

Rust中文翻译21的相关文章

Rust中文翻译19

我们来讨论一下迭代器. 还记得Rust的for循环么?有一个例子: for x in 0..10 { println!("{}", x); } 现在你更了解Rust了,我们可以讨论它的工作细节了.区间(Ranges)(0..10)就是迭代器.一个迭代器可以重复的调用.next()方法,然后给我们返回一个序列. 像这样: let mut range = 0..10; loop { match range.next() { Some(x) => { println!("{}

Rust中文翻译7

去台湾玩儿了一个礼拜,赶紧回来继续翻译吧! 3.1.5 循环 Page 38 loop关键字可以实现一个无限循环.让我们来加入一个循环: extern crate rand; use std::io; use std::cmp::Ordering; use rand::Rng; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1, 101); prin

Rust中文翻译29

5.10 生命期 本节是Rust三处描述所有权系统的其中之一.所有权是Rust最独特和引人注目的特性,这也是Rust程序员必须熟悉的一个特性.所有权使Rust得以实现它最大的设计目标,内存安全.这里有一些不同的概念,每一个都有自己的章节: 所有权,你正在读的 借用(borrowing, 5.9), 以及它的关联特性'引用' 生命期(5.10),以及borrowing的高级特性 这三者是相关的,也是循序渐进的.你必须要完全理解这个三个部分. 5.10.1 元 在我们讨论细节之前,有两个关于所有权系

Rust中文翻译12

Page 63 我们已经学习了如何写一些Rust代码了.但是能写Rust代码和能写好的Rust代码又很大区别. 这一章讲述一些相对独立的指南,告诉你如何把的Rust代码更进一步.一些常用的模式和标准库会被提到.你可以随意安排阅读本章的顺序. 4.1 栈和堆 作为一个系统级别的语言,Rust可以在底层进行操作.如果你来自于高层语言,有一些系统编程概念你可能不太熟悉.最重要的就是内存中的栈和堆是如何工作的.如果你了解类C语言中栈的分配,这一章就是一个回顾.如果你不了解,你可以在本章学习这些概念了,但

Rust中文翻译28

5.9 引用和借用 本节是Rust三处描述所有权系统的其中之一.所有权是Rust最独特和引人注目的特性,这也是Rust程序员必须熟悉的一个特性.所有权使Rust得以实现它最大的设计目标,内存安全.这里有一些不同的概念,每一个都有自己的章节: 所有权,你正在读的 借用(borrowing, 5.9), 以及它的关联特性'引用' 生命期(5.10),以及borrowing的高级特性 这三者是相关的,也是循序渐进的.你必须要完全理解这个三个部分. 5.9.1 元 在我们讨论细节之前,有两个关于所有权系

Rust中文翻译17

4.2.4 文档测试 没有什么比带有示例的文档更好的了.也没有什么比不能工作的例子更糟的了,因为有可能文档中的代码已经修改了.为此,Rust支持自动测试我们示例代码.让我们看一个富有血肉的src/lib.rs的例子: //! The àdder` crate provides functions that add numbers to other numbers. //! //! # Examples //! //! ``` //! assert_eq!(4, adder::add_two(2)

Rust中文翻译15

大多数使用垃圾回收的语言都默认在堆上分配内存.这就意味着每个值都要装箱.有很多原因导致他们这样设计,但是这超出了本书的范围.同样,也有很多优化设计导致它并不是100%这样工作的.垃圾回收器宁愿选择在堆上分配内存,也不使用栈和Drop操作来释放内存. 4.1.7 该用哪一种呢? 所以当栈又快又好用的时候,为什么我们还需要使用堆呢?一个重要的原因是,栈只给你提供了LIFO语义来管理内存.堆分配却可以提供更加通用的和任意的顺序来使用内存,同时附带一些开销. 通常情况下,你应当使用栈,Rust默认就是如

Rust中文翻译20

Page 100 并发和并行在计算机科学中是非常重要的主题.在工业领域也很火.计算机如今有越来越多的核心,然而很多程序员还没有准备好使用它们. Rust安全的内存特性同样适用于并发存储.并发的Rust代码也是内存安全的,没有数据竞争.Rust的类型系统会保证这一点,给你提供了有利的帮助在编译时实现并发代码. 在我们开始讨论Rust的并发之前,我们需要理解一个很重要的事情:Rust是一个低级别语言,它的所有功能都是通过库来提供的,而不是语言自身.也就是说你不喜欢Rust处理并发的方式,你完全可以实

Rust中文翻译34

5.14 Match 经常,一个简单的if/else还不够,因为你可能会有多于两种情况.而且,条件会变得更加复杂.Rust有一个关键字,match,允许你替代复杂的if/else组合,来实现一些更强大的功能.看一下: let x = 5; match x { 1 => println!("one"), 2 => println!("two"), 3 => println!("three"), 4 => println!(&