喜欢的话别忘了点赞、收藏加关注哦,对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)
9.3.1. 传播错误
当你编写的函数中包含了一些可能会执行失败的调用时,除了在函数里处理这个错误,还可以把错误返回给调用者,让它来决定如何进一步处理这个错误。
看个例子:
use std::fs::File;
use std::io::{self, Read}; fn read_username_from_file() -> Result<String, 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() -> Result<String, 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)
)。也就是说,第五行代码的效果等同于:
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::connvert::From
这个trait,而它的作用是在错误之间进行转换,将一个错误类型转化为另外一个错误类型,而被?
所接收的错误,会隐式地被from
函数处理,from
会看当前代码所在的函数的返回值的错误类型是什么,然后转换为什么。
就以刚才的代码为例,read_username_from_file
函数的返回值是Result<String, io::Error>
,from
函数就看得出来函数需要io::Error
作为发生错误时的返回值,就会把不同的错误类型转化为io::Error
,这里只是碰巧所有的函数体内的错误类型都是io::Error
,就不需要转化这一步。
这个特点用于针对不同的错误原因,返回同一种错误类型的情况非常有用。但前提条件是涉及到的错误类型实现了转换为所返回的错误类型的from
函数就可以。
9.3.4. 链式调用
其实之前的例子还可以继续优化,就是使用链式调用的形式。优化后的代码如下:
use std::fs::File;
use std::io::{self, Read}; fn read_username_from_file() -> Result<String, 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类型的函数
看个例子:
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: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()`
help: consider adding return type|
2 ~ fn main() -> Result<(), Box<dyn std::error::Error>> {
3 | let result = File::open("6657.txt")?;
4 + Ok(())|
报错内容是?
运算符只能用于返回值是Result或者Option这类实现了Try这个trait的类型,而main
函数的返回类型是()
,也就是单元类型,相当于什么也没返回。
但是,谁说main
函数的返回类型一定是单元类型呢?只要把它的返回值改成Result类型不就完了吗?代码如下:
use std::error::Error;
use std::fs::File; fn main() -> Result<(), Box<dyn Error>> { let result = File::open("6657.txt")?; Ok(())
}
-
把返回类型改为
Result<(), Box<dyn Error>>
,也就是说如果程序正常运行,会返回Ok
这个变体,里面呢包裹着单元类型;如果没有正常运行,会返回Err
这个变体,包裹着Box<dyn Error>
(其中的Error
是std::error::Error
),这是一个trait对象,在以后会讲,这里可以把它简单地理解为任何可能的错误类型。 -
如果能成功读取,那么
?
就会把包裹在Ok
里的文件数据返回赋给result
,然后继续执行,Ok(())
是main
函数里的最后一个表达式,它返回了Ok
这个变体,同时把单元类型包裹着。 -
如果不能成功读取,那么
?
就会把Err(e)
作为main
函数的返回值返回回去,并且函数执行到此结束。