Rust for cpp devs - 错误处理

Rust 将软件中的错误分为两个类型:可恢复错误和不可恢复错误。

对于可恢复错误,例如文件找不到,可以报告给调用者处理。而不可恢复错误,例如内存访问越界,则应该直接终止程序。

大部分语言并不区分这两者,而是统一使用 exception 机制。Rust 没有 exception,对于可恢复错误,Rust 使用 Result<T, E> 作为返回值,对于不可恢复错误,用 panic! 宏终止程序。

不可恢复的错误使用 panic!

某些错误是调用者无法修复的,只能让程序终止。这时候我们使用 panic! 宏:

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

错误信息是:

thread 'main' panicked at 'crash and burn', src/main.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Using a panic! Backtrace

我们常常遇到的情况是被调用者 panic,而非手动 panic。如:

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

结果是:

thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src/main.rs:3:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

提示我们可以通过 RUST_BACKTRACE 环境变量来跟踪到底什么触发了错误:

RUST_BACKTRACE=1 cargo run

可恢复错误使用 Result

对于使用方法错误(如用户输入的参数错误),使用 panic! 不是很合适。此外,大部分错误没有严重到需要让程序停止运行。这种情况下可以使用 Result 类型作为返回值:

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

例如,如果我们要打开一个文件,需要处理文件不存在的情况:

use std::fs::File;

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

这时候如果路径下不存在 file.txt 这个文件,则会报错:

thread 'main' panicked at 'Problem opening the file: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/main.rs:8:23
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

语法糖:unwrapexpect

上面代码的 pattern 非常常见,因此 Result<T, E> 类型定义了一些方法方法来减少开发者的劳动。

  • unwrap:如果 Ok,返回 Ok 中包含的值,如果 Err,调用 panic!
use std::fs::File;

fn main() {
    let f = File::open("hello.txt").unwrap();
}
  • expect:类似于 unwrap,但是允许我们自定义提示信息。
use std::fs::File;

fn main() {
    let f = File::open("hello.txt").expect("Failed to open hello.txt");
}

传递错误

有时我们希望将错误交给调用者处理,此时,我们就需要传递(propagating)这个错误。

use std::fs;
use std::io;
use std::io::Read;
use std::process;

fn read_username_from_file(filename: &str) -> Result<String, io::Error> {
    let f = fs::File::open(filename);

    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut s = String::new();

    let result = match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    };

    return result;
}

fn main() {
    let filename = "a.txt";
    match read_username_from_file(filename) {
        Ok(s) => println!("read from file {} succeeded, {}", filename, s),
        Err(e) => {
            println!("read from file {} failed: {}", filename, e);
            process::exit(1);
        }
    };
}

read_username_from_file 函数内部并不处理错误,而是通过返回一个 Result<T, E> 类型来让调用者处理。在这里,错误类型是 io::Error,可能由 File::openread_to_string 方法引发。当返回错误时,调用者可以根据需求编写处理逻辑,例如使用默认用户名,或是像上面代码一样报错退出。

传递错误的语法糖:? 操作符

传递错误的逻辑非常常见,因此 Rust 提供了 ? 来简化代码。

Result 值后加 ? 的效果是:

  • Ok:返回 Ok 中的值,并继续执行
  • Err:跳出整个函数,并返回错误

因此,上面的函数可以简化为:

fn read_username_from_file(filename: &str) -> Result<String, io::Error> {
    let mut f = fs::File::open(filename)?;

    let mut s = String::new();

    f.read_to_string(&mut s)?;

    return Ok(s);
}

? 操作符已经省略了许多重复代码,它还支持链式调用,更加简洁:

fn read_username_from_file(filename: &str) -> Result<String, io::Error> {
    let mut s = String::new();

    fs::File::open(filename)?.read_to_string(&mut s)?;

    return Ok(s);
}

不指定错误类型

如果我们不关心错误类型,我们可以将io::Error 替换为 Box<dyn std::error::Error>。它可以是任何实现了 Error trait 的类型,约等于所有的错误类型。

fn read_username_from_file(filename: &str) -> Result<String, Box<dyn std::error::Error>> {
    let mut s = String::new();

    fs::File::open(filename)?.read_to_string(&mut s)?;

    return Ok(s);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容