3.4 rust 異常處理


 

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,

官方對env::var的解釋

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

這是為了表示從方法參數開始,到方法返回值,這個范圍的變量,生命周期相同。

 

下面這篇文章的錯誤處理方法非常詳細,但由於太詳細了,不適合初學者看,初學者需要先掌握常用的幾種先用着就可以了

【譯】Rust 中的錯誤處理


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM