【Rust自学】12.4. 重构 Pt.2:错误处理

12.4.0. 写在正文之前

第12章要做一个实例的项目------一个命令行程序。这个程序是一个grep(Global Regular Expression Print ),是一个全局正则搜索和输出的工具。它的功能是在指定的文件中搜索出指定的文字。

这个项目分为这么几步:

  • 接收命令行参数
  • 读取文件
  • 重构:改进模块和错误处理(本文)
  • 使用TDD(测试驱动开发)开发库功能
  • 使用环境变量
  • 将错误信息写入标准错误而不是标准输出

喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)

12.4.1. 回顾

上一节中为了模块化我们为变量创建了结构体,还把读取指令的函数独立出去改成了结构体的方法。以下是截止到上一篇文章所写出的所有代码:

rust 复制代码
use std::env;  
use std::fs;  
  
struct Config {  
    query: String,  
    filename: String,  
}  
  
fn main() {  
    let args:Vec<String> = env::args().collect();  
    let config = Config::new(&args);  
  
    let contents = fs::read_to_string(config.filename)  
        .expect("Somthing went wrong while reading the file");  
    println!("With text:\n{}", contents);  
}  
  
impl Config {  
    fn new(args: &[String]) -> Config {  
        let query = args[1].clone();  
        let filename = args[2].clone();  
        Config {  
            query,  
            filename,  
        }  
    }  
}

12.4.2. 意料之外的输入

这个程序能正确运行的前提是用户输入的输入无误,那我们试试不带参数的输入会引发什么:

复制代码
$ cargo run
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
     Running `target/debug/minigrep`
thread 'main' panicked at src/main.rs:27:21:
index out of bounds: the len is 1 but the index is 1
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

它提示"Index out of bound"索引越界,作为程序编写者的我们明白这是因为参数不够导师程序在使用索引获取参数时越界触发恐慌。但是作为用户就不可能看懂这个报错信息,无法纠正错误。

这一篇文章要做的就是让程序产生的错误信息易于理解。

12.4.3. 指定报错信息

让用户理解报错信息的方式就是自己指定一个报错信息。刚刚的例子是在运行Config::new时索引越界,所以我们就修改这个地方:

rust 复制代码
impl Config {  
    fn new(args: &[String]) -> Config {  
        if args.len() < 3 {  
            panic!("Not enough arguments");  
        }  
        let query = args[1].clone();  
        let filename = args[2].clone();  
        Config {  
            query,  
            filename,  
        }  
    }  
}

如果args的元素数量小于三就发生恐慌打印"Not enough arguments"来提示用户输入的参数太少了。

再试试不带参数的输入:

复制代码
$ cargo run
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
     Running `target/debug/minigrep`
thread 'main' panicked at src/main.rs:26:13:
not enough arguments
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

这一次的错误信息比上一次就好很多了。

但是它仍然残留了一些其他的信息,比如"thread 'main' panicked at src/main.rs:26:13:"和"note: run with RUST_BACKTRACE=1 environment variable to display a backtrace",这些内容是给程序员看的不是给用户看的。所以这些信息也得去掉。

12.4.4. 使用Result类型

panic!适用于程序本身出现问题时的恐慌,而这里却少参数的输入是程序使用时的问题,针对这种问题,使用Result类型来传播错误(这部分的内容详见 9.2. Result枚举与可恢复的错误 Pt.1 & Pt.2)才是最优解:

rust 复制代码
impl Config {  
    fn new(args: &[String]) -> Result<Config, &'static str> {  
        if args.len() < 3 {  
            return Err("Not enough arguments");  
        }  
        let query = args[1].clone();  
        let filename = args[2].clone();  
        Ok(Config { query, filename})  
    }  
}
  • 报错的信息需要用Err包裹,成功的返回值需要用Ok包裹
  • Result类型的Ok返回Config实例,Err返回&str字符串字面值,但是编译器不知道这个&str是从哪里来的以及它的生命周期有多长,所以得带生命周期,我们需要它在程序运行时始终保持有效,写成&'static str这个静态生命周期。

new函数的返回值都变了,main函数里接收值的逻辑也得变:

rust 复制代码
let config = Config::new(&args).unwrap_or_else(|err| {  
    println!("Problem parsing arguments: {}", err);  
    process::exit(1);  
});

unwrap_or_else这个方法会接收Result类型,如果是Ok,就会把Ok附带的值直接返回赋给变量,类似于unwrap;如果是Err,那么这个方法会调用一个闭包(closure)。

闭包是我们定义的匿名函数,并将其作为参数传递给unwrap_or_else。其写法是两个管道符||,在中间放变量名,相当于一个参数,这里就放了err,这个err可以在闭包的函数体内被调用,比如在打印错误时就使用了err

然后使用标准库的process::exit这个函数,使用前记得先导入一下:use std::process;,如果调用exit函数,程序的执行就会立即终止,而其参数,也就是示例代码中的1就作为程序退出时的状态码,这样显示到println!("Problem parsing arguments: {}", err);之后程序就会终止,自然就不会有比如"thread 'main' panicked at src/main.rs:26:13:"和"note: run with RUST_BACKTRACE=1 environment variable to display a backtrace"这些内容。

试一下:

复制代码
$ cargo run
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.48s
     Running `target/debug/minigrep`
Problem parsing arguments: not enough arguments

闭包这个概念在下一章才会讲到,这里没看懂也没关系,只要了解个大概即可。

12.4.5. 整体代码

以下是截止到这篇文章写出的所有代码:

rust 复制代码
use std::env;  
use std::fs;  
use std::process;  
  
struct Config {  
    query: String,  
    filename: String,  
}  
  
fn main() {  
    let args:Vec<String> = env::args().collect();  
    let config = Config::new(&args).unwrap_or_else(|err| {  
        println!("Problem parsing arguments: {}", err);  
        process::exit(1);  
    });  
  
    let contents = fs::read_to_string(config.filename)  
        .expect("Somthing went wrong while reading the file");  
    println!("With text:\n{}", contents);  
}  
  
impl Config {  
    fn new(args: &[String]) -> Result<Config, &'static str> {  
        if args.len() < 3 {  
            return Err("Not enough arguments");  
        }  
        let query = args[1].clone();  
        let filename = args[2].clone();  
        Ok(Config { query, filename})  
    }  
}
相关推荐
leobertlan2 小时前
2025年终总结
前端·后端·程序员
面向Google编程3 小时前
从零学习Kafka:数据存储
后端·kafka
冷雨夜中漫步3 小时前
Python快速入门(6)——for/if/while语句
开发语言·经验分享·笔记·python
易安说AI3 小时前
Claude Opus 4.6 凌晨发布,我体验了一整晚,说说真实感受。
后端
易安说AI4 小时前
Ralph Loop 让Claude无止尽干活的牛马...
前端·后端
易安说AI4 小时前
用 Claude Code 远程分析生产日志,追踪 Claude Max 账户被封原因
后端
颜酱5 小时前
图结构完全解析:从基础概念到遍历实现
javascript·后端·算法
m0_736919105 小时前
C++代码风格检查工具
开发语言·c++·算法
2501_944934735 小时前
高职大数据技术专业,CDA和Python认证优先考哪个?
大数据·开发语言·python
黎雁·泠崖6 小时前
【魔法森林冒险】5/14 Allen类(三):任务进度与状态管理
java·开发语言