Unrecoverable Errors with panic!
Sometimes, bad things happen in your code, and there’s nothing you can do about it. In these cases, Rust has the panic!
macro. When the panic!
macro executes, your program will print a failure message, unwind and clean up the stack, and then quit. This most commonly occurs when a bug of some kind has been detected and it’s not clear to the programmer how to handle the error.
如果遇到异常时不进行栈内存释放处理,即遇错程序直接退出,则需要设置panic = 'abort'
Unwinding the Stack or Aborting in Response to a Panic By default, when a panic occurs, the program starts unwinding, which means Rust walks back up the stack and cleans up the data from each function it encounters. But this walking back and cleanup is a lot of work. The alternative is to immediately abort, which ends the program without cleaning up. Memory that the program was using will then need to be cleaned up by the operating system. If in your project you need to make the resulting binary as small as possible, you can switch from unwinding to aborting upon a panic by adding panic = 'abort' to the appropriate [profile] sections in your Cargo.toml file. For example, if you want to abort on panic in release mode, add this: [profile.release] panic = 'abort'
panic!
pub fn p1(){ panic!(" error !"); }
thread 'main' panicked at ' error !', src/test/pan.rs:5:5 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
$ export RUST_BACKTRACE=1 $ cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.01s Running `target/debug/hongyun RUST_BACKTRACE=1` ------------ thread 'main' panicked at ' error !', src/test/pan.rs:5:5 stack backtrace: 0: backtrace::backtrace::libunwind::trace at /cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.46/src/backtrace/libunwind.rs:86 1: backtrace::backtrace::trace_unsynchronized at /cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.46/src/backtrace/mod.rs:66 2: std::sys_common::backtrace::_print_fmt at src/libstd/sys_common/backtrace.rs:78 3: <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt at src/libstd/sys_common/backtrace.rs:59 4: core::fmt::write at src/libcore/fmt/mod.rs:1076 5: std::io::Write::write_fmt at src/libstd/io/mod.rs:1537 6: std::sys_common::backtrace::_print at src/libstd/sys_common/backtrace.rs:62 7: std::sys_common::backtrace::print at src/libstd/sys_common/backtrace.rs:49 8: std::panicking::default_hook::{{closure}} at src/libstd/panicking.rs:198 9: std::panicking::default_hook at src/libstd/panicking.rs:217 10: std::panicking::rust_panic_with_hook at src/libstd/panicking.rs:526 11: std::panicking::begin_panic at /home/tanpengfei3/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/panicking.rs:456 12: hongyun::test::pan::p1 at src/test/pan.rs:5 13: hongyun::main at src/main.rs:7 14: std::rt::lang_start::{{closure}} at /home/tanpengfei3/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:67 15: std::rt::lang_start_internal::{{closure}} at src/libstd/rt.rs:52 16: std::panicking::try::do_call at src/libstd/panicking.rs:348 17: std::panicking::try at src/libstd/panicking.rs:325 18: std::panic::catch_unwind at src/libstd/panic.rs:394 19: std::rt::lang_start_internal at src/libstd/rt.rs:51 20: std::rt::lang_start at /home/tanpengfei3/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:67 21: main 22: __libc_start_main 23: _start note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
程序异常
fn main() { let v = vec![1, 2, 3]; v[99]; }
Here, we’re attempting to access the 100th element of our vector (which is at index 99 because indexing starts at zero), but it has only 3 elements. In this situation, Rust will panic. Using []
is supposed to return an element, but if you pass an invalid index, there’s no element that Rust could return here that would be correct.
In C, attempting to read beyond the end of a data structure is undefined behavior. You might get whatever is at the location in memory that would correspond to that element in the data structure, even though the memory doesn’t belong to that structure. This is called a buffer overread and can lead to security vulnerabilities if an attacker is able to manipulate the index in such a way as to read data they shouldn’t be allowed to that is stored after the data structure.
To protect your program from this sort of vulnerability, if you try to read an element at an index that doesn’t exist, Rust will stop execution and refuse to continue.
Recoverable Errors with Result
#![allow(unused)] fn main() { enum Result<T, E> { Ok(T), Err(E), } }
T
represents the type of the value that will be returned in a success case within the Ok
variant, and E
represents the type of the error that will be returned in a failure case within the Err
variant.
注意Result是一个枚举,枚举的值类型是相同的,即Ok,Err两个对象的类型都是Result,在函数参数传值或返回值的时候,会用到这个特性
use std::fs::File; use std::io; use std::io::Read; pub fn read_from_file() -> Result<String, io::Error> { let f = File::open("/tmp/test2.log"); 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), } }
文件读取返回的对象是 std::result::Result,枚举,里面包装两个值,正确的,错误的
In the case where File::open
succeeds, the value in the variable f
will be an instance of Ok
that contains a file handle. In the case where it fails, the value in f
will be an instance of Err
that contains more information about the kind of error that happened.
Let’s look at the return type of the function first: Result<String, io::Error>
. This means the function is returning a value of the type Result<T, E>
where the generic parameter T
has been filled in with the concrete type String
and the generic type E
has been filled in with the concrete type io::Error
. If this function succeeds without any problems, the code that calls this function will receive an Ok
value that holds a String
—the username that this function read from the file. If this function encounters any problems, the code that calls this function will receive an Err
value that holds an instance of io::Error
that contains more information about what the problems were.
如果文件不存在报以下错误就返回,也可以不用return,而是创建该文件
Err(Os { code: 2, kind: NotFound, message: "No such file or directory" })
对错误进行分类
use std::fs::File;
use std::io::ErrorKind;
pub fn open_with_create(){
let fil: String= "/tmp/hello.txt".to_string();
let f = File::open(&fil);
let f = match f {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create(&fil) {
Ok(fc) => fc,
Err(e) => panic!("Problem creating the file: {:?}", e),
},
other_error => {
panic!("Problem opening the file: {:?}", other_error)
}
},
};
}
Shortcuts for Panic on Error: unwrap
and expect
If the Result
value is the Ok
variant, unwrap
will return the value inside the Ok
. If the Result
is the Err
variant, unwrap
will call the panic!
use std::fs::File; fn main() { let f = File::open("hello.txt").unwrap(); }
Another method, expect
, which is similar to unwrap
, lets us also choose the panic!
error message. Using expect
instead of unwrap
and providing good error messages can convey your intent and make tracking down the source of a panic easier.
use std::fs::File; fn main() { let f = File::open("hello.txt").expect("Failed to open hello.txt"); }
Because this error message starts with the text we specified, Failed to open hello.txt
, it will be easier to find where in the code this error message is coming from. If we use unwrap
in multiple places, it can take more time to figure out exactly which unwrap
is causing the panic because all unwrap
calls that panic print the same message.
当代码或错误比较多的时候,.expect("某某方法报某某异常"),可以快速地定位哪段代码出了问题,而wnwarp的错误大多是一样的。
简写一
pub fn read_file1() -> Result<String, io::Error> { let f = File::open("/tmp/a.log"); let mut f = match f { Ok(file) => file, Err(e) => return Err(e), }; let mut s = String::new(); f.read_to_string(&mut s)?; Ok(s) }
If the value of the Result
is an Ok
, the value inside the Ok
will get returned from this expression, and the program will continue. If the value is an Err
, the Err
will be returned from the whole function as if we had used the return
keyword so the error value gets propagated to the calling code.
error values that have the ?
operator called on them go through the from
function, defined in the From
trait in the standard library, which is used to convert errors from one type into another. When the ?
operator calls the from
function, the error type received is converted into the error type defined in the return type of the current function.
the ?
at the end of the File::open
call will return the value inside an Ok
to the variable f
. If an error occurs, the ?
operator will return early out of the whole function and give any Err
value to the calling code.
如果发生异常,? 会自动调用一个叫from的方法,把异常的类型进行转换,我们得到的将是一个异常的输出,比如
let res = tools::fil::read_file1(); println!("res:{:?}",res);
如果文件不存在 ,则输出以下内容
res:Err(Os { code: 2, kind: NotFound, message: "No such file or directory" })
文件存在的输出
res:Ok("asb\n")
Result<String, io::Error> 返回值只有一个,本人学过Java,开始总是下意识地认为这是一个键值对,是两个值,不是的,这是rust、rust、rust!!!
Result<String, io::Error> 是枚举,之所以有两个类型,是因为枚举的不同值可以是不同的类型(同一类型的枚举,不同的其他类型),rust中Result<String, io::Error>只会返回一个String或io::Error类型的枚举值
简写二
pub fn read_file2() -> Result<String, io::Error> { let mut s = String::new(); File::open("/tmp/a.log")?.read_to_string(&mut s)?; Ok(s) }
We’ve moved the creation of the new String
in s
to the beginning of the function; that part hasn’t changed. Instead of creating a variable f
, we’ve chained the call to read_to_string
directly onto the result of File::open("hello.txt")?
. We still have a ?
at the end of the read_to_string
call, and we still return an Ok
value containing the username in s
when both File::open
and read_to_string
succeed rather than returning errors.
简写三
pub fn read_file3() -> Result<String, io::Error> { fs::read_to_string("/tmp/a.log") }
Reading a file into a string is a fairly common operation, so Rust provides the convenient fs::read_to_string
function that opens the file, creates a new String
, reads the contents of the file, puts the contents into that String
, and returns it.
从指定文件中搜索一个字符串
该例子包含了一些错误处理的方法技巧
#![allow(unused)] use std::env; use std::error::Error; use std::fs; #[derive(Debug)] pub struct Config { pub query: String, pub filename: String, pub case_sensitive: bool, } impl Config { //成功返回一个struct实例,异常则返回一个字符串切片 pub fn new(args: &[String]) -> Result<Config, &str> { if args.len() < 3 { return Err("not enough arguments"); } let query = args[1].clone(); let filename = args[2].clone(); let case_sensitive = env::var("CASE_INSENSITIVE").is_err(); Ok(Config { query, filename, case_sensitive, }) } } fn run(config: Config) -> Result<(), Box<dyn Error>> { let contents = fs::read_to_string(config.filename)?; let results = if config.case_sensitive { search(&config.query, &contents) } else { search_case_insensitive(&config.query, &contents) }; for line in results { println!("{}", line); } Ok(()) } fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { let mut results = Vec::new(); for line in contents.lines() { if line.contains(query) { results.push(line); } } results } fn search_case_insensitive<'a>( query: &str, contents: &'a str, ) -> Vec<&'a str> { let query = query.to_lowercase(); let mut results = Vec::new(); for line in contents.lines() { if line.to_lowercase().contains(&query) { results.push(line); } } results } //从Result中将struct实例取出来 fn get_config(result: Result<Config, &str>) -> Config { match result { Ok(n) => n, Err(e) => panic!("over"), } } pub fn test(){ let args = [String::from("search"),String::from("bb"),String::from("/tmp/logs/aa.log")]; let cfg_res = Config::new(&args); let cfg = get_config(cfg_res); print!("{:#?}\n",cfg); print!("{:?}\n",cfg.case_sensitive); run(cfg); }
调用test方法输出
------------------------ Config { query: "bb", filename: "/tmp/logs/aa.log", case_sensitive: true, } true bb to bb
ai@aisty:/tmp/logs$ cat /tmp/logs/aa.log
aa
aa
bb to bb
pub fn new(args: &[String]) -> Result<Config, &str>
new方法初始化一个struct实例,返回一个Result,是个枚举,异常时则是返回一个字符串(可以看到具体发生了什么错);
fn get_config(result: Result<Config, &str>) -> Config
get_config是从Result取出需要的数据,因Result实际上是一个枚举,不是Config
env::var: 从当前的线程中读取变量,返回的是一个Result,
pub fn var<K: AsRef<OsStr>>(key: K) -> Result<String, VarError>
如果我在程序中定义了CASE_INSENSITIVE变量,那么is_err()就是false了
let CASE_INSENSITIVE = false; let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
这说明Result是一个is_err()方法,可以判断返回是不是异常;
Box用于在堆上创建数据,有Box<T>,Box::new(T)等用法,Box<dyn Error>则是不必去关心具体错误的细节,只要错误继承了std::error::Error就可以了,比如
fn test2() -> Result<(), Box<dyn Error>>{ //这里写了很多代码,不确定是否就有错,错误返回就写上一个Box<dyn Error> let a = 5; let b = a - 5; let c = 3/b; // let c = 3/(b+1); print!("{}\n",c); Ok(()) } pub fn test3(){ let a = test2(); print!("result:{}\n",a.is_err()); let b = match a { Err(e) => format!("{:#?}",e), Ok(()) => String::from("") }; print!("Box<dyn Error>---------:{}",b); }
()就是空元组,相当于占个位置,Ok中的()要与Result的第一个参数()对上,意思就是如果程序无异常,返回个空元组
&'a str
这是为了表示从方法参数开始,到方法返回值,这个范围的变量,生命周期相同。
下面这篇文章的错误处理方法非常详细,但由于太详细了,不适合初学者看,初学者需要先掌握常用的几种先用着就可以了