9.3 Result枚举与可恢复的错误 Pt.2传播错误、问号运算符与链式调用9.3.1 传播错误当你编写的函数中包含了一些可能会执行失败的调用时除了在函数里处理这个错误还可以把错误返回给调用者让它来决定如何进一步处理这个错误。看个例子use std::fs::File; use std::io::{self, Read}; fn read_username_from_file() - ResultString, io::Error { let f File::open(6657.txt); let mut f match f { Ok(file) file, Err(e) return Err(e), }; let mut s String::new(); match f.read_to_string(mut s) { Ok(_) Ok(s), Err(e) Err(e), } } fn main() { let result read_username_from_file(); }这段代码的意图是从文件中读取用户名- 它的返回类型是Result枚举。它的两个类型参数T和E对应String和io::Error。也就是说当一切顺利时函数会返回Result下的Ok变体Ok里包裹着String类型的用户名如果遇到了问题这个函数就会返回Result下的Err变体在这个变体里会包含io::Error的实例。再看函数体首先使用File::open尝试打开一个文件把Result赋给f然后对f进行match操作这里把第二个f设为可变是因为下文的read_to_string会使用mut self。如果操作成功会返回file并把值赋给f如果操作失败就会return Err(e)。这里的e就是具体发生的错误而在函数体里面遇到return关键字就表示函数执行到此为止返回return后面的值也就是Err(e)。错误类型恰好是io::Error所以返回值符合Result的类型参数。如果File::open操作成功接下来函数就创建了一个可变的String叫s然后调用read_to_string把文件里的内容读取到s里面。当然read_to_string也可能会失败所以后面还跟了一个match表达式。这个match表达式结尾没有分号它也是这个函数的最后一个表达式所以它就是这个函数的返回值。这个match有两个分支如果操作成功就返回Result的Ok变体并且把String类型的s封装到里面如果操作失败就返回Err变体把错误e包裹在里面返回。而read_to_string的返回错误类型恰好也是io::Error所以返回值符合Result的类型参数。9.3.2?运算符在Rust里传播错误非常常见所以Rust专门提供了?运算符来简化这个过程。使用?实现上文例子的同样效果use std::fs::File; use std::io::{self, Read}; fn read_username_from_file() - ResultString, io::Error { let mut f File::open(6657.txt)?; let mut s String::new(); f.read_to_string(mut s)?; Ok(s) } fn main() { let result read_username_from_file(); }对于第一个?第5行File::open返回的是Result加上?的意思是如果File::open返回Ok那么包裹在Ok里的值就会作为表达式的结果赋给f如果File::open返回Err那么就会终止函数的执行把Err及里面包裹的错误信息作为整个函数的返回值返回——也就是return Err(e)。换句话说第5行代码的效果等同于let f File::open(6657.txt); let mut f match f { Ok(file) file, Err(e) return Err(e), };对于第二个?第7行如果read_to_string操作成功就会继续往下执行。成功的返回值实际上在代码中没有用到而如果执行失败就会终止函数的执行把Err及里面包裹的错误信息作为整个函数的返回值返回——也就是return Err(e)。如果前面都操作成功那么表达式Ok(s)会把String类型的s包裹在Ok里返回。总结一下把?用于Result时如果是Ok那么Ok中的值就是表达式的结果然后程序继续执行如果操作失败也就是Err那么Err就是整个函数的返回值就像使用了return。9.3.3?与from函数Rust提供了from函数。它来自std::convert::From这个trait作用是在错误之间进行转换将一个错误类型转化为另外一个错误类型。被?所接收的错误会隐式地被from处理from会查看当前函数应当返回的错误类型是什么然后转换为什么。就以刚才的代码为例read_username_from_file的返回值是ResultString, io::Error所以from看得出来函数需要io::Error作为发生错误时的返回类型就会把不同的错误类型转化为io::Error。这里只是碰巧函数体内的所有错误类型都已经是io::Error所以不需要转化这一步。这个特点在需要把不同的错误原因映射成同一种错误类型时非常有用。前提条件是涉及到的错误类型实现了Fromtrait从而可以转换为所返回的错误类型。9.3.4 链式调用其实之前的例子还可以继续优化就是使用链式调用。优化后的代码如下use std::fs::File; use std::io::{self, Read}; fn read_username_from_file() - ResultString, io::Error { let mut s String::new(); File::open(6657.txt)?.read_to_string(mut s)?; Ok(s) } fn main() { let result read_username_from_file(); }刚刚说过了把?用于Result时如果是Ok那么Ok中的值就是表达式的结果然后程序继续执行。那就可以消除原代码中赋值的步骤直接使用链式调用。9.3.5?只能用于返回Result或Option的函数看个例子use std::fs::File; fn main() { let result File::open(6657.txt)?; }输出error[E0277]: the ? operator can only be used in a function that returns Result or Option (or another type that implements FromResidual) -- src/main.rs:3:40 | 2 | fn main() { | --------- this function should return Result or Option to accept ? 3 | let result File::open(6657.txt)?; | ^ cannot use the ? operator in a function that returns () | help: consider adding return type | 2 ~ fn main() - Result(), Boxdyn std::error::Error { 3 | let result File::open(6657.txt)?; 4 Ok(()) |报错内容是?运算符只能用于返回值是Result或Option这类实现了Trytrait的类型而main的返回类型是()也就是单元类型相当于什么也没返回。但是谁说main的返回类型一定是单元类型呢只要把它的返回类型改成Result不就解决了吗代码如下use std::error::Error; use std::fs::File; fn main() - Result(), Boxdyn Error { let result File::open(6657.txt)?; Ok(()) }把返回类型改为Result(), Boxdyn Error也就是说如果程序正常运行会返回Ok变体里面包裹着单元类型如果没有正常运行会返回Err变体包裹着Boxdyn Error其中的Error是std::error::Error。这是一个trait对象以后会讲这里可以把它简单地理解为任何可能的错误类型。如果能成功读取文件那么?就会把包裹在Ok里的文件数据返回赋给result然后继续执行。Ok(())是main里的最后一个表达式它返回Ok变体同时把单元类型包裹着。如果不能成功读取那么?就会把Err(e)作为main的返回值返回回去并且函数执行到此结束。