16. 一个I/O项目:构建命令行程序(上)

一、功能

  • grep 最简单的使用场景是在特定文件中搜索指定字符串;
  • 程序的运行结果是能在文件中寻找目标字符串;
  • 使用cargo new minigrep创建新项目;

二 、接受命令行参数

use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    println!("{:?}",args);
    
    let query = &args[1];
    let filename = &args[2];
    println!("Search for {}", query);
    println!("In file {}", filename);
}
  • std::env::args返回一个传递给程序行参数的迭代器;
  • 调用迭代器的collect方法将会得到一个集合;
  • 集合的第一个值是当前程序的名称,后面的值是传递给程序的参数;

运行结果
在这里插入图片描述

  • 红框标注的就是所有的参数列表;
  • 然后分别输出了queryfilename的值;
  • 程序就是要在文件filename中查找query对应的字符串;

三、 读取文件

  1. 创建文件poem.txt文件,内容如下
I'm nobody! Who are you?
Are you nobody, too?
Then there's a pair of us - don't tell!
They'd banish us, you know.

How dreary to be somebody!
How public, like a frog
To tell your name the livelong day
To an admiring bog!
  1. 为main.rs增加打开文件的代码
use std::fs;
use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    // println!("{:?}",args);
    
    let query = &args[1];
    let filename = &args[2];
    // println!("Search for {}", query);
    println!("In file {}", filename);

    let contents = fs::read_to_string(filename).expect("Something went wrong reading the file");
    println!("With text:\n{}", contents);
}
  • 引入std::fs,需要用它的read_to_string函数;
  • 注释不需要的打印
  • 使用cargo run needly poem.txt运行程序(目前needly参数没有起作用)

下面的运行结果显示实现了文件读取的功能
在这里插入图片描述

四、重构改进模块性和错误处理

程序目前有四个问题需要修复:

  • 分离解析参数与打开文件功能;
  • 添加配置变量的结构;
  • 添加更多的文件打开失败原因;
  • 添加专用的错误处理函数

4.1 二进制项目的关注分离

  • main函数变得庞大时进程二进制分离的指导步骤:
  1. 将程序拆分成 main.rs 和 lib.rs 并将程序的逻辑放入lib.rs中。
  2. 当命令行解析逻辑比较小时,可以保留在main.rs中。
  3. 当命令行解析开始变得复杂时,也同样将其从 main.rs 提取到lib.rs中。
  • 经过这些处理之后保留在main函数中的函数功能被限制为:
  1. 使用参数值调用命令行解析逻辑;
  2. 设置任何其他的配置;
  3. 调用 lib.rs 中的 run 函数;
  4. 如果 run 返回错误,则处理这个错误;
  • 总之:main.rs处理程序运行,lib.rs处理所有的真正的任务逻辑;

4.2 提取参数解析器

use std::fs;
use std::env;

struct Config{
    query: String,
    filename: String,
}

fn parse_config(args: &[String]) -> Config {
    let query = args[1].clone();
    let filename = args[2].clone();

    Config {query, filename}
}

fn main() {
    let args: Vec<String> = env::args().collect();
    
    let config = parse_config(&args);

    let contents = fs::read_to_string(config.filename)
                        .expect("Something went wrong reading the file!");

    println!("contents: \n{}", contents);
}

  • 现在的代码还全都在main.rs里边;
  • 将filename和query组合成Config结构体;
  • 结构体中使用完整的、拥有所有权的String类型,因此在parse_config函数中进行clone操作;

4.3 创建一个Config的构造函数

  • 使用之前学过的方法,为结构体构建一个构造函数;
  • 将parse_config的功能移到构建函数里去;
use std::fs;
use std::env;

struct Config{
    query: String,
    filename: String,
}

impl Config{
    fn new(args: &[String]) -> Config{
        let query = args[1].clone();
        let filename = args[2].clone();
        Config{query, filename}
    }
}

fn main() {
    let args: Vec<String> = env::args().collect();
    
    let config = Config::new(&args);

    let contents = fs::read_to_string(config.filename)
                        .expect("Something went wrong reading the file!");

    println!("contents: \n{}", contents);
}

4.4 传参错误处理

如果new中的参数个数小于3时,会产生错误,添加代码

 fn new(args: &[String]) -> Config{
     assert!(!args.len() < 3, "Not enough arguments");
	 ……
 }
  • assert!宏只有当条件为假时会调用panic!
  • 因此习惯写做”对满足panic!的条件取反";
  • panic!的调用更趋向于程序上的问题而不是使用上的问题;
  • 可以返回一个可以表明成功或错误的Result;

从 new 中返回 Result

  • 返回一个 Result 值,它在成功时会包含一个Config 的实例,错误时会描述问题。
use std::process;

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

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("Something went wrong reading the file!");

    println!("contents: \n{}", contents);
}
  • new函数返回一个Result,在成功时带有一个Config实例失败时带有一个&'static str
  • &'static str就代表字符串字面量;
  • unwrap_or_else定义于Result<T, E> 上,使用它可以进行一些自定义的非 panic! 的错误处理;
  • 当Result 是Ok时,它返回 Ok 内部封装的值;
  • 当Result是Err时,该方法会调用一个闭包(closure),也就是一个我们定义的作为参数传递给 unwrap_or_else的匿名函数;

4.5 从main中提取逻辑

  • 创建run 的函数来存放目前main函数中不属于设置配置或处理错误的所有逻辑;
fn run(config: Config) -> Result<(), Box<dyn Error>> {
    let contents = fs::read_to_string(config.filename)?;

    println!("With text:\n{}", contents);

    Ok(())
}

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

    if let Err(e) = run(config) {
        println!("Application error: {}", e);

        process::exit(1);
    }
}
  • run函数获取一个Config实例作为参数;
  • run函数的返回类型设置为Result<(), Box<dyn Error>>
  • run函数Ok时返回unit 类型 (),失败时返回了trait对象Box<dyn Error>,这无需指定具体将会返回的值的类型,采用动态识别;
  • run函数里的read_to_string后取消了expect而采用?运算符,这会从函数中返回错误值并由调用者来处理;
  • run成功时返回一个将unit类型值包装的Ok值,并没有什么实际意义;
  • main函数中使用if let检查run返回的是否是一个Err;

4.6 将代码拆分进crate

将main.rs中的下述功能都移动到src/lib.rs中

  • run 函数定义
  • 相关的 use 语句
  • Config 的定义
  • Config::new 函数定义
    src/lib.rs
use std::fs;
use std::error::Error;

pub struct Config{
    query: String,
    filename: String,
}

impl Config{
    pub 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})
    }
}

pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
    let contents = fs::read_to_string(config.filename)?;

    println!("With text:\n{}", contents);

    Ok(())
}

src/main.rs

use std::env;
use std::process;
use minigrep::Config;

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

    if let Err(e) = minigrep::run(config) {
        println!("Application error: {}", e);

        process::exit(1);
    }
}

相关推荐

  1. 12-输入/输出项目构建命令程序

    2024-06-18 17:02:06       50 阅读
  2. 构建一个springboot项目

    2024-06-18 17:02:06       46 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-06-18 17:02:06       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-06-18 17:02:06       106 阅读
  3. 在Django里面运行非项目文件

    2024-06-18 17:02:06       87 阅读
  4. Python语言-面向对象

    2024-06-18 17:02:06       96 阅读

热门阅读

  1. linux上运行js脚本

    2024-06-18 17:02:06       28 阅读
  2. bwip-js-条码生成-常见条码类型-常用参数设置

    2024-06-18 17:02:06       35 阅读
  3. 学生成绩管理系统:

    2024-06-18 17:02:06       30 阅读
  4. ffmpeg压缩视频

    2024-06-18 17:02:06       28 阅读
  5. 公有云和私有云有什么区别?详情介绍有关内容

    2024-06-18 17:02:06       29 阅读
  6. OpenGL绘制Bezier曲线

    2024-06-18 17:02:06       33 阅读
  7. Mybatis和Hibernate的作用区别及底层原理分析

    2024-06-18 17:02:06       34 阅读
  8. 李宏毅深度学习项目——HW1个人笔记

    2024-06-18 17:02:06       26 阅读
  9. Linux 常用命令 - rm 【删除文件或目录】

    2024-06-18 17:02:06       35 阅读