zoukankan      html  css  js  c++  java
  • [易学易懂系列|rustlang语言|零基础|快速入门|(20)|错误处理]

    [易学易懂系列|rustlang语言|零基础|快速入门|(20)|错误处理]

    实用知识

    错误处理

    我们今天来讲讲Rust中的错误处理。

    很多语言都有自己的错误处理方式,比如,java是异常处理机制。

    Rust有自己独特的错误处理机制。

    在Rust有两种错误: recoverable and unrecoverable errors.

    翻译成中文就是:可恢复错误和不可恢复错误。

    Rust分别用两种方式来处理这两种错误:

    1.Result用来处理可恢复错误,这种方式一般不会直接退出当前程序。

    2.宏panic!用来处理不可恢复错误,这种方式一般会直接退出当前程序

    什么是宏?

    宏是用来生成代码的,在调用宏的地方,编译器会先将宏进行展开,生成代码,然后再编译展开后的代码。

    我们接下来的一篇文章会讲到,现在只要记住它是用来生成代码的,就行了。

    回到正题。

    我们先来看看panic!,先看看简单的代码:

    fn main() {
        panic!("crash and burn");
    }
    

    运行代码,会产生这样的结果:

    $ cargo run
       Compiling panic v0.1.0 (file:///projects/panic)
        Finished dev [unoptimized + debuginfo] target(s) in 0.25s
         Running `target/debug/panic`
    thread 'main' panicked at 'crash and burn', src/main.rs:2:5
    note: Run with `RUST_BACKTRACE=1` for a backtrace.
    

    这里的信息,明确地指出panic发生的代码行。

    我们再看看另一个std标准库的例子:

    fn main() {
        let v = vec![1, 2, 3];
    
        v[99];
    }
    

    运行代码,我们又会产生这样的结果:

    $ cargo run
       Compiling panic v0.1.0 (file:///projects/panic)
        Finished dev [unoptimized + debuginfo] target(s) in 0.27s
         Running `target/debug/panic`
    thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', libcore/slice/mod.rs:2448:10
    note: Run with `RUST_BACKTRACE=1` for a backtrace.
    

    这段代码,直接访问超出vector下标的元素,系统直接报告一个不可恢复的错误,并直接退出程序。

    我们再来看看Result。

    先看看简单的例子:

    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>`
    

    这段错误信息告诉我们,我们的代码要有返回值 :Result<T, E>

    那好,我们来修改代码:

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

    当然,我们这里也可以直接用另一种写法(Result的unwrap),这种 写法我们在下面会详细说明 :

    use std::fs::File;
    
    fn main() {
        let f = File::open("hello.txt").unwrap();
    }
    

    我们可以用模式匹配来处理,这种方法比java等语言的代码,简洁多了。漂亮!

    我们还是从显式的模式匹配代码看看逻辑:

     let _f = match f {
            Ok(file) => file,
            Err(error) => {
                panic!("Problem opening the file: {:?}", error)
            },
        };
    

    如果文件hello.txt在当前工程目录存在,匹配到:Ok,并返回文件句柄( file handle)。

    如果文件不存在,就直接返回错误(panic!),并直接退出当前程序。

    好,运行代码,得出结果:

    thread 'main' panicked at 'Problem opening the file: Os { code: 2, kind: NotFound, message: "系统找不到指定的文件。" }', srcmain.rs:123:23
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
    error: process didn't exit successfully: `targetdebughello.exe` (exit code: 101)
    

    因为当前目录不存在文件hello.txt,所以程序直接返回panic错误。

    好理解。

    好,我们现在再改造一下代码:

    use std::fs::File;
    use std::io::ErrorKind;
    
    fn main() {
        let f = File::open("hello.txt");
    
        let _fh = match f {
            Ok(file) => file,
            Err(error) => match error.kind() {
                ErrorKind::NotFound => match File::create("hello.txt") {
                    Ok(fc) => fc,
                    Err(e) => panic!("Problem creating the file: {:?}", e),
                },
                other_error => panic!("Problem opening the file: {:?}", other_error),
            },
        };
    }
    

    我们再对不同的error再进行细粒度处理。

    1.如果文件存在,就创建一个文件:hello.txt,那创建的过程中,又会出现 两种 情况 :

    1.1创建成功,直接返回文件句柄。

    1.2创建失败,直接调用panic!宏。

    运行代码, 程序会自己在工程根目录创建一个空的文件:hello.txt。

    我们看到上面 的代码,太多模式匹配,有点复杂,能不能再简洁一点?

    可以的,请看下面代码:

    use std::fs::File;
    use std::io::ErrorKind;
    
    fn main() {
        let f = File::open("hello.txt").unwrap_or_else(|error| {
            if error.kind() == ErrorKind::NotFound {
                File::create("hello.txt").unwrap_or_else(|error| {
                    panic!("Problem creating the file: {:?}", error);
                })
            } else {
                panic!("Problem opening the file: {:?}", error);
            }
        });
    }
    

    在Rust,我们用闭包让代码再简洁可读。完美!

    其中unwrap_or_else方法,是Result这个枚举的方法。

    它主要的用法是,打开Result,如果OK,就直接取OK中的内容,否则,

    如果它是Err,则执行后面的闭包,请看基本用法的简单例子:

    fn count(x: &str) -> usize { x.len() }
    
    assert_eq!(Ok(2).unwrap_or_else(count), 2);
    assert_eq!(Err("foo").unwrap_or_else(count), 3);
    

    好的。

    回到我们的panic错误处理。

    Rust有两种简洁写法:unwrap and expect

    我们来一一分析下,先来看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
    

    这里错误信息都是默认信息: message: "No such file or directory"

    能不能自定义呢?

    可以的。

    用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
    

    这里的错误信息,就我们自定义的:Failed to open hello.txt

    很好!

    用自定义的错误信息,可以让我们更好地定位问题。

    错误信息的传播

    我们再来看看这样的代码:

    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),
        }
    }
    

    这里我们定义一个方法,用来从文件中读username。

    这里我们用了模式匹配代码,逻辑很清楚,但很复杂很繁琐。

    有没有更好的写法?

    有的。

    在Rust,我们可以这样写:

    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)
    }
    

    所有模式匹配处理错误的代码,都可以用?替换,简洁很多!很好!

    有没有更简洁的方式 ?

    有的。

    请看下面代码:

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

    我们可以用链式方法。漂亮!

    当然,更好的方式,是直接用标准库里的方法,那就更简洁了:

    use std::io;
    use std::fs;
    
    fn read_username_from_file() -> Result<String, io::Error> {
        fs::read_to_string("hello.txt")
    }
    fn main() {
        let r = read_username_from_file();
        println!("the string in hello.txt is {:?}", r);
    }
    

    实际项目开发中,我们更推荐直接用标准库里的方法。因为标准库的方法,更少出错,经过更多测试,久经考验!

    我们这里要特别强调一点就是:?只能用于有返回值并且返回值为Result<T,E>(或Option)的方法。

    不信?

    我们来做做实验:

    use std::fs::File;
    
    fn main() {
        let f = File::open("hello.txt")?;
    }
    

    运行上面的代码,会报错误:

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

    怎么办?

    改造一下代码:

    use std::error::Error;
    use std::fs::File;
    
    fn main() -> Result<(), Box<dyn Error>> {
        let f = File::open("hello.txt")?;
    
        Ok(())
    }
    

    这里的Box<>,是智能指针,它用来封装不同的错误类型。

    现在main函数可以正常编译了。

    以上,希望对你有用。

    如果遇到什么问题,欢迎加入:rust新手群,在这里我可以提供一些简单的帮助,加微信:360369487,注明:博客园+rust
    

    参考文章:

    https://doc.rust-lang.org/stable/book/ch09-02-recoverable-errors-with-result.html

    https://learning-rust.github.io/docs/e2.panicking.html

  • 相关阅读:
    洗礼灵魂,修炼python(48)--巩固篇—模块
    洗礼灵魂,修炼python(48)--巩固篇—模块
    洗礼灵魂,修炼python(48)--巩固篇—模块
    Excel中拆分列
    Excel中拆分列
    Excel中拆分列
    Excel中拆分列
    Eclipse新建类的时候如何自动添加注释(作者,时间,版本等信息)
    Eclipse新建类的时候如何自动添加注释(作者,时间,版本等信息)
    用golang实现DDOS攻击网站
  • 原文地址:https://www.cnblogs.com/gyc567/p/12034092.html
Copyright © 2011-2022 走看看