ch9. Error Handling

@Kais(VagrantPi)

tags: Rust, 簡報, Rust 讀書會

Agenda

  • Unrecoverable Errors with panic!
  • Recoverable Errors with Result
  • To panic! or Not To panic!

Unrecoverable Errors with panic!


Rust 中的錯誤

  • 可恢復錯誤(recoverable)
  • 不可恢復錯誤(unrecoverable)

Panic 又可分為兩種模式:

  • 展開(unwinding)預設值
  • 終止(abort)

Cargo.toml 的 [profile] 部分增加 panic = ‘abort’


fn main() { panic!("crash and burn"); }
$ cargo run
   Compiling rust v0.1.0 (file:///Users/kais.lin/rust)
    Finished dev [unoptimized + debuginfo] target(s) in 3.42 secs
     Running `target/debug/rust`
thread 'main' panicked at 'crash and burn', main.rs:2:5
 note: Run with `RUST_BACKTRACE=1` for a backtrace.

使用 panic! 的 backtrace

fn main() { let v = vec![1, 2, 3]; v[99]; }
$ cargo run
   Compiling rust v0.1.0 (file:///Users/kais.lin/rust)
    Finished dev [unoptimized + debuginfo] target(s) in 0.34 secs
     Running `target/debug/rust`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', /Users/travis/build/rust-lang/rust/src/libcore/slice/mod.rs:865:10
 note: Run with `RUST_BACKTRACE=1` for a backtrace.

$ RUST_BACKTRACE=1 cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target/debug/rust`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', /Users/travis/build/rust-lang/rust/src/libcore/slice/mod.rs:865:10
stack backtrace:
   0: std::sys::unix::backtrace::tracing::imp::unwind_backtrace
             at libstd/sys/unix/backtrace/tracing/gcc_s.rs:49
   1: std::sys_common::backtrace::print
             at libstd/sys_common/backtrace.rs:71
             at libstd/sys_common/backtrace.rs:59
   2: std::panicking::default_hook::{{closure}}
             at libstd/panicking.rs:207
   3: std::panicking::default_hook
             at libstd/panicking.rs:223
   4: std::panicking::begin_panic
             at libstd/panicking.rs:402
   5: std::panicking::try::do_call
             at libstd/panicking.rs:349
   6: std::panicking::try::do_call
             at libstd/panicking.rs:325
   7: core::ptr::drop_in_place
             at libcore/panicking.rs:72
   8: core::ptr::drop_in_place
             at libcore/panicking.rs:58
   9: <usize as core::slice::SliceIndex<[T]>>::index
             at /Users/travis/build/rust-lang/rust/src/libcore/slice/mod.rs:865
  10: core::slice::<impl core::ops::index::Index<I> for [T]>::index
             at /Users/travis/build/rust-lang/rust/src/libcore/slice/mod.rs:767
  11: <alloc::vec::Vec<T> as core::ops::index::Index<I>>::index
             at /Users/travis/build/rust-lang/rust/src/liballoc/vec.rs:1648
  12: rust::main
             at ./main.rs:4
  13: std::rt::lang_start::{{closure}}
             at /Users/travis/build/rust-lang/rust/src/libstd/rt.rs:74
  14: std::panicking::try::do_call
             at libstd/rt.rs:59
             at libstd/panicking.rs:306
  15: panic_unwind::dwarf::eh::read_encoded_pointer
             at libpanic_unwind/lib.rs:102
  16: <std::io::Write::write_fmt::Adaptor<'a, T> as core::fmt::Write>::write_str
             at libstd/panicking.rs:285
             at libstd/panic.rs:361
             at libstd/rt.rs:58
  17: std::rt::lang_start
             at /Users/travis/build/rust-lang/rust/src/libstd/rt.rs:74
  18: rust::main
12: rust::main
             at ./main.rs:4

Recoverable Errors with Result


面對它

使用 Result 類型來處理潛在錯誤

enum Result<T, E> {
    Ok(T),
    Err(E),
}

如何知道某某函數會回傳 Rusult 呢?除文件之外,書中也提到了另一個方法


執行它

use std::fs::File; fn main() { let f = File::open("hello.txt"); }
error[E0308]: mismatched types
 --> src/main.rs:4:18
  |
4 |     let f: u32 = File::open("hello.txt");
  |                  ^^^^^^^^^^^^^^^^^^^^^^^ expected u32, found enum
`std::result::Result`
  |
  = note: expected type `u32`
             found type `std::result::Result<std::fs::File, std::io::Error>`

處理它

use std::fs::File; fn main() { let f = File::open("hello.txt"); let f = match f { Ok(file) => file, Err(error) => { panic!("There was a problem opening the file: {:?}", error) }, }; }

use std::fs::File; use std::io::ErrorKind; fn main() { let f = File::open("hello.txt"); let f = match f { Ok(file) => file, Err(ref error) if error.kind() == ErrorKind::NotFound => { match File::create("hello.txt") { Ok(fc) => fc, Err(e) => { panic!( "Tried to create file but there was a problem: {:?}", e ) }, } }, Err(error) => { panic!( "There was a problem opening the file: {:?}", error ) }, }; }

if error.kind() == ErrorKind::NotFound 被稱為 match guard,用來更完善 match 的而外條件判斷


失敗時 panic 的簡寫:unwrap

use std::fs::File; fn main() { let f = File::open("hello.txt").unwrap(); }
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error {
repr: Os { code: 2, message: "No such file or directory" } }',
src/libcore/result.rs:906:4

失敗時 panic 的簡寫:expect

use std::fs::File; fn main() { let f = File::open("hello.txt").expect("Failed to open hello.txt"); }
thread 'main' panicked at 'Failed to open hello.txt: Error { repr: Os { code:
2, message: "No such file or directory" } }', src/libcore/result.rs:906:4

傳播(propagating)錯誤

簡單說就是 function 會回傳 error,這樣可以讓呼叫他的地方能夠判斷要不要做錯誤處裡

use std::io; use std::io::Read; use std::fs::File; fn read_username_from_file() -> Result<String, io::Error> { let f = File::open("hello.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), } }

傳播錯誤的簡寫: ?

use std::io; use std::io::Read; use std::fs::File; fn read_username_from_file() -> Result<String, io::Error> { let mut f = File::open("hello.txt")?; let mut s = String::new(); f.read_to_string(&mut s)?; Ok(s) }

與 match 的差別

原文理解不能,以下是我個人理解,請大大們勘誤

? 會將錯誤值丟給 from 函數(定義於 From trait 中),可以將 Result 回傳的錯誤型別,轉換成該 fn 回傳的錯誤型別


use std::io::{self, Read}; use std::num; enum CliError { IoError(io::Error), ParseError(num::ParseIntError), } impl From<io::Error> for CliError { fn from(error: io::Error) -> Self { CliError::IoError(error) } } impl From<num::ParseIntError> for CliError { fn from(error: num::ParseIntError) -> Self { CliError::ParseError(error) } } fn open_and_parse_file(file_name: &str) -> Result<i32, CliError> { let mut file = std::fs::File::open("test")?; let mut contents = String::new(); file.read_to_string(&mut contents)?; let num: i32 = contents.trim().parse()?; Ok(num) }

trait.From


? 只能被用于返回 Result 的函数

use std::fs::File; fn main() { let f = File::open("hello.txt")?; }
error[E0277]: the trait bound `(): std::ops::Try` is not satisfied
 --> src/main.rs:4:13
  |
4 |     let f = File::open("hello.txt")?;
  |             ------------------------
  |             |
  |             the `?` operator can only be used in a function that returns
  `Result` (or another type that implements `std::ops::Try`)
  |             in this macro invocation
  |
  = help: the trait `std::ops::Try` is not implemented for `()`
  = note: required by `std::ops::Try::from_error`

To panic! or Not To panic!


Examples, Prototype Code, and Tests

有時候只是要快速的驗證或者是 demo,可以先 unwrap 之類的方法,未來再來實現這部廢的錯誤處理


當你比編譯器知道更多情況

use std::net::IpAddr; let home: IpAddr = "127.0.0.1".parse().unwrap();

即便是寫死的,而且確定是可分析的 ip,編譯器仍會覺得還是可能會出現 Err 因此要我們處理 Result,這邊種情況下使用 unwrap 是合適的


當輸入值錯誤時

預期外的傳入值,可能導致存取非當前資料的記憶體空間,而出現安全性問題

不過透過 Rust 的 type system 就會為你做很多檢查了(EX: u32 接到的值不會有負的)


建立自訂義的型別驗證

v0.0.1

loop { // --snip-- let guess: i32 = match guess.trim().parse() { Ok(num) => num, Err(_) => continue, }; if guess < 1 || guess > 100 { println!("The secret number will be between 1 and 100."); continue; } match guess.cmp(&secret_number) { // --snip-- }

不過這樣但這樣每次都要進一次判斷式,如果很多的函式都需要這樣的檢查,這樣寫就恨很冗餘(也許也會小小的引響到效能)


pub struct Guess { value: u32, } impl Guess { pub fn new(value: u32) -> Guess { if value < 1 || value > 100 { panic!("Guess value must be between 1 and 100, got {}.", value); } Guess { value } } pub fn value(&self) -> u32 { self.value } }

v0.0.2

extern crate rand; use std::io; use std::cmp::Ordering; use rand::Rng; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1, 101); loop { println!("Please input your guess."); let mut guess = String::new(); io::stdin().read_line(&mut guess) .expect("Failed to read line"); let guess: u32 = match guess.trim().parse() { - Ok(num) => num, + Ok(num) => Guess::new(num).value(), Err(_) => continue, }; println!("You guessed: {}", guess); match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => { println!("You win!"); break; } } } } + pub struct Guess { + value: u32, + } + + impl Guess { + pub fn new(value: u32) -> Guess { + if value < 1 || value > 100 { + panic!("Guess value must be between 1 and +100, got {}.", value); + } + + Guess { + value + } + } + + pub fn value(&self) -> u32 { + self.value + } + }

Thanks for listening ~

Select a repo