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
這是為了表示從方法參數開始,到方法返回值,這個范圍的變量,生命周期相同。
下面這篇文章的錯誤處理方法非常詳細,但由於太詳細了,不適合初學者看,初學者需要先掌握常用的幾種先用着就可以了