Rust学习-构建命令行程序

这篇具有很好参考价值的文章主要介绍了Rust学习-构建命令行程序。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Rust 的运行速度、安全性、单二进制文件输出和跨平台支持使其成为创建命令行程序的绝佳选择
本文以实现一个minigrep为例,展开对之前学习的回归

初版

接收命令行参数并打印文件内容

// 当所需函数嵌套了多于一层模块时,通常将父模块引入作用域
// std::env::args 在其任何参数包含无效 Unicode 字符时会 panic
// 如果需要接受包含无效 Unicode 字符的参数,使用 std::env::args_os
// 它返回 OsString 值,且OsString 值每个平台都不一样
use std::env;
use std::fs;

fn main() {
	// env::args()返回一个传递给程序的命令行参数的 迭代器(iterator)
    let args: Vec<String> = env::args().collect();
    
    // 程序的名称占据了 vector 的第一个值 args[0],和C的命令行参数相同
    
    let query = &args[1];
    let filename = &args[2];

    println!("Searching 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);
}

问题

(1)main 进行了两个任务,函数功能不单一
(2)query 和 filename 是程序中的配置变量,和代码中其他变量混到一起
(3)打开文件失败使用 expect 来打印出错误信息,没有得到失败原因
(4)使用 expect 来处理不同的错误,如果用户没有指定足够的参数来运行程序,则展示的错误依旧无法让使用者阅读

解决方式-关注分离

main的职责:
(1)使用参数值调用命令行解析逻辑
(2)设置任何其他的配置
(3)调用 lib.rs 中的 run 函数
(4)如果 run 返回错误,则处理这个错误

main.rs 处理程序运行
lib.rs 处理所有的真正的任务逻辑
因为不能直接测试 main 函数,这个结构通过将所有的程序逻辑移动到 lib.rs 的函数中使得可以测试他们

重构

重构参数读取

方案一

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

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

    println!("Searching for {}", config.query);
    println!("In file {}", config.filename);

    let contents = fs::read_to_string(config.filename)
        .expect("Something went wrong reading the file");
        
    println!("With text:\n{}", contents);
}

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

// 定义 Config 来包含拥有所有权的 String 值
// main 中的 args 变量是参数值的所有者并只允许 parse_config 函数借用他们
// 意味着如果 Config 尝试获取 args 中值的所有权将违反 Rust 的借用规则
fn parse_config(args: &[String]) -> Config {
    // 由于其运行时消耗,尽量避免使用 clone 来解决所有权问题
    let query = args[1].clone();
    let filename = args[2].clone();

    Config { query, filename }
}

更合理的参数读取

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

fn main() {
    // args类型是:alloc::vec::Vec<alloc::string::String>
    let args: Vec<String> = env::args().collect();

    let config = Config::new(&args);

    println!("Searching for {}", config.query);
    println!("In file {}", config.filename);

    let contents = fs::read_to_string(config.filename)
        .expect("Something went wrong reading the file");
        
    println!("With text:\n{}", contents);
}

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

impl Config {
    // args类型是 &[alloc::string::String]
    // 使用这个方式也可以:fn new(args: &Vec<String>) -> Config
    // 此时argos类型为:&alloc::vec::Vec<alloc::string::String>
    // 这种转换有待后续深挖
    fn new(args: &[String]) -> Config {
        let query = args[1].clone();
        let filename = args[2].clone();
        Config { query, filename }
    }
}

改善错误信息

执行 cargo run test,直接panic

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

糟糕的方案一

fn new(args: &[String]) -> Config {
    if args.len() < 3 {
        panic!("not enough arguments"); // 不友好
    }
    ......

// panic信息如下
// thread 'main' panicked at 'not enough arguments', src/main.rs:26:13
}

方案二

返回一个 Result
成功时带有一个 Config 实例
出错时带有一个 &'static str:字符串字面量

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

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

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

    // unwrap_or_else 定义于标准库的 Result<T, E> 
    // 使用它可以进行一些自定义的非 panic! 的错误处理
    // 当其值是 Err 时,该方法会调用一个 闭包(closure),即匿名函数
    // unwrap_or_else 会将 Err 的内部值,即静态字符串传递给|err|
    let config = Config::new(&args).unwrap_or_else(|err| {
        println!("Problem parsing arguments: {}", err);
        // process::exit 会立即停止程序并将传递给它的数字作为退出状态码
        process::exit(1);
    });
    
    println!("Searching for {}", config.query);
    println!("In file {}", config.filename);

    let contents = fs::read_to_string(config.filename)
        .expect("Something went wrong reading the file");
        
    println!("With text:\n{}", contents);
}

// 打印信息如下
// Problem parsing arguments: not enough arguments

main 函数处理 new 函数返回的 Result 值
并在出现错误的情况更明确的结束进程

精简main

main的修改

fn main() {
    // --snip--

    println!("Searching for {}", config.query);
    println!("In file {}", config.filename);

    // 其他处理逻辑全部放入run函数
    // if let 来检查 run 是否返回一个 Err 值
    // run 并不返回像 Config::new 返回的 Config 实例那样需要 unwrap 的值
    // 因为 run 在成功时返回 ()
    // 而只关心检测错误,所以并不需要 unwrap_or_else 来返回未封装的值
    // 因为它只会是 ()
    if let Err(e) = run(config) {
        println!("Application error: {}", e);
        process::exit(1);
    }
}

run的处理

// 引入 trait 对象 Box<dyn Error>的路径
use std::error::Error;
// --snip--

// unit 类型 ():作为 Ok 时的返回值类型
// trait 对象 Box<dyn Error>:
// 返回实现了 Error trait 的类型,无需指定具体将会返回的值的类型
// 因为在不同的错误场景可能有不同类型的错误返回值
fn run(config: Config) -> Result<(), Box<dyn Error>> {
    // 不同于遇到错误就 panic!
    // ? 会从函数中返回错误值并让调用者来处理它
    let contents = fs::read_to_string(config.filename)?;

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

    // 调用 run 函数只是为了它的副作用;函数并没有返回什么有意义的值
    Ok(())
}

拆模块

拥有可以测试的公有 API 的库 crate
逻辑提取到了 src/lib.rs
所有的参数解析和错误处理留在了 src/main.rs

直接使用多种参数调用函数并检查返回值,无需从命令行运行二进制文件

// src/lib.rs
use std::error::Error;
use std::fs;

pub struct Config {
    pub query: String,
    pub 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>> {
    println!("Searching for {}", config.query);
    println!("In file {}", config.filename);

    // 不同于遇到错误就 panic!
    // ? 会从函数中返回错误值并让调用者来处理它
    let contents = fs::read_to_string(config.filename)?;
    println!("With text:\n{}", contents);

    // 调用 run 函数只是为了它的副作用;函数并没有返回什么有意义的值
    Ok(())
}
// main.rs
use std::env;
use std::process;

use rust_minigrep::Config;

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

    // unwrap_or_else 定义于标准库的 Result<T, E>
    // 使用它可以进行一些自定义的非 panic! 的错误处理
    // 当其值是 Err 时,该方法会调用一个 闭包(closure),即匿名函数
    // unwrap_or_else 会将 Err 的内部值,即静态字符串传递给|err|
    let config = Config::new(&args).unwrap_or_else(|err| {
        println!("Problem parsing arguments: {}", err);
        // process::exit 会立即停止程序并将传递给它的数字作为退出状态码
        process::exit(1);
    });

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

TDD开发搜索功能

测试驱动开发(Test Driven Development, TDD)

use std::error::Error;
use std::fs;

pub struct Config {
    pub query: String,
    pub 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>> {
    println!("Searching for {}", config.query);
    println!("In file {}", config.filename);

    // 不同于遇到错误就 panic!
    // ? 会从函数中返回错误值并让调用者来处理它
    let contents = fs::read_to_string(config.filename)?;
    println!("With text:\n{}", contents);

    // 调用 run 函数只是为了它的副作用;函数并没有返回什么有意义的值
    Ok(())
}

// 在 search 的签名中定义一个显式生命周期 'a 并用于 contents 参数和返回值
// 告诉 Rust 函数 search 返回的数据将与 search 函数中的参数 contents 的数据存在的一样久
// 为了使这个引用有效那么 被 slice 引用的数据也需要保持有效
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    vec![]
}

// 如下则编译失败
// Rust 不可能知道需要的是哪一个参数,所以需要明确告诉它
// 参数 contents 包含了所有的文本而且希望返回匹配的那部分文本,所以contents 应该要使用生命周期语法来与返回值相关联的参数
// 其他语言中并不需要你在函数签名中将参数与返回值相关联
// pub fn search(query: &str, contents: &str) -> Vec<&str> {

// 先编写测试用例
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn one_result() {
        let query = "duct";
        let contents = "\
Rust:
safe, fast, productive.
Pick three.";

        assert_eq!(
            vec!["safe, fast, productive."],
            search(query, contents)
        );
    }
}

上述铁定测试失败,因为没有开发search模块,所以search的功能如下:
(1)遍历内容的每一行文本
(2)查看这一行是否包含要搜索的字符串
(3)如果有,将这一行加入列表返回值中
(4)如果没有,什么也不做。
(5)返回匹配到的结果列表

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    let mut results = Vec::new();

    // contents.lines 返回一个迭代器
    for line in contents.lines() {
        // 字符串的contains方法检查包含操作
        if line.contains(query) {
            results.push(line);
        }
    }

    results
}

最终lib.rs内容如下

use std::error::Error;
use std::fs;

pub struct Config {
    pub query: String,
    pub 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>> {
    println!("Searching for {}", config.query);
    println!("In file {}", config.filename);

    // 不同于遇到错误就 panic!
    // ? 会从函数中返回错误值并让调用者来处理它
    let contents = fs::read_to_string(config.filename)?;
    println!("With text:\n{}", contents);

    for line in search(&config.query, &contents) {
        println!("{}", line);
    }

    // 调用 run 函数只是为了它的副作用;函数并没有返回什么有意义的值
    Ok(())
}

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    let mut results = Vec::new();

    for line in contents.lines() {
        if line.contains(query) {
            results.push(line);
        }
    }

    results
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn one_result() {
        let query = "duct";
        let contents = "\
Rust:
safe, fast, productive.
Pick three.";

        assert_eq!(
            vec!["safe, fast, productive."],
            search(query, contents)
        );
    }
}

错误信息打印到标准输出流

目前为止将所有的输出都 println! 到了终端
大部分终端都提供了两种输出:
标准输出(standard output,stdout)对应一般信息
标准错误(standard error,stderr)则用于错误信息

cargo run > output.txt
shell将所有信息存储到 output.txt
结果output.txt中存储了错误信息

将错误打印到标准错误

// 标准库提供了 eprintln! 宏来打印到标准错误流
 eprintln!("Problem parsing arguments: {}", err);

添加区分大小写的功能

设置环境变量来设置搜索是否是大小写敏感

vi ~/.zshrc
添加如下一行
export RUST_CASE_INSENSITIVE=1

env
查看是否设置成功
RUST_CASE_INSENSITIVE=1
use std::error::Error;
use std::fs;
use std::env;

pub struct Config {
    pub query: String,
    pub filename: String,
    pub case_sensitive: bool, // 新增加控制字段
}

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();
        // 处理环境变量的函数位于标准库的 env 模块中
        let case_sensitive = env::var("RUST_CASE_INSENSITIVE").is_err();
        Ok(Config { query, filename, case_sensitive })
    }
}

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

    let results = if config.case_sensitive {
        search(&config.query, &contents)
    } else {
        search_case_insensitive(&config.query, &contents)
    };

    for line in results {
        println!("{}", line);
    }

    Ok(())
}

pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    let query = query.to_lowercase();
    let mut results = Vec::new();

    for line in contents.lines() {
        if line.to_lowercase().contains(&query) {
            results.push(line);
        }
    }

    results
}

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    let mut results = Vec::new();

    for line in contents.lines() {
        if line.contains(query) {
            results.push(line);
        }
    }

    results
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn case_sensitive() {
        let query = "duct";
        let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";

        assert_eq!(
            vec!["safe, fast, productive."],
            search(query, contents)
        );
    }

    #[test]
    fn case_insensitive() {
        let query = "rUsT";
        let contents = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";

        assert_eq!(
            vec!["Rust:", "Trust me."],
            search_case_insensitive(query, contents)
        );
    }
}

使用迭代器重构

// main.rs
use std::env;
use std::process;

use rust_minigrep::Config;

fn main() {
    // unwrap_or_else 定义于标准库的 Result<T, E>
    // 使用它可以进行一些自定义的非 panic! 的错误处理
    // 当其值是 Err 时,该方法会调用一个 闭包(closure),即匿名函数
    // unwrap_or_else 会将 Err 的内部值,即静态字符串传递给|err|
    let config = Config::new(env::args()).unwrap_or_else(|err| {
        println!("Problem parsing arguments: {}", err);
        // process::exit 会立即停止程序并将传递给它的数字作为退出状态码
        process::exit(1);
    });

    if let Err(e) = rust_minigrep::run(config) {
        println!("Application error: {}", e);
        process::exit(1);
    }
}
// lib.rs

use std::error::Error;
use std::fs;
use std::env;

pub struct Config {
    pub query: String,
    pub filename: String,
    pub case_sensitive: bool, // 新增加控制字段
}

impl Config {
    pub fn new(mut args: std::env::Args) -> Result<Config, &'static str> {
        args.next();

        let query = match args.next() {
            Some(arg) => arg,
            None => return Err("Didn't get a query string"),
        };

        let filename = match args.next() {
            Some(arg) => arg,
            None => return Err("Didn't get a file name"),
        };

        let case_sensitive = env::var("RUST_CASE_INSENSITIVE").is_err();

        println!("query={}", query);
        println!("filename={}", filename);
        println!("case_sensitive={}", case_sensitive);

        Ok(Config { query, filename, case_sensitive })
    }
}

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

    let results = if config.case_sensitive {
        println!("run case senstive");
        search(&config.query, &contents)
    } else {
        println!("run case insenstive");
        search_case_insensitive(&config.query, &contents)
    };

    for line in results {
        println!("搜索结果: {}", line);
    }

    Ok(())
}

pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    let query = query.to_lowercase();
    let mut results = Vec::new();

    for line in contents.lines() {
        if line.to_lowercase().contains(&query) {
            results.push(line);
        }
    }

    results
}

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    // 字符串数组的迭代器
    contents.lines()
        // 留下true的,去掉false的
        .filter(|line| line.contains(query))
        // 收集结果
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn case_sensitive() {
        let query = "duct";
        let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";

        assert_eq!(
            vec!["safe, fast, productive."],
            search(query, contents)
        );
    }

    #[test]
    fn case_insensitive() {
        let query = "rUsT";
        let contents = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";

        assert_eq!(
            vec!["Rust:", "Trust me."],
            search_case_insensitive(query, contents)
        );
    }
}

附录

打印类型

fn print_type_of<T>(_: &T) {
    println!("{}", std::any::type_name::<T>())
}

环境变量的设置

vi ~/.bash_profile
vi ~/.zshrc

需要设置哪个?
echo $SHELL

系统安装了哪些shell
cat /etc/shells

mac 中使用 zsh,部分因为 oh-my-zsh 配置集,兼容 bash,还能自动补全。
sh 是 unix 上的标准 shell,很多 unix 版本都配有它
bash由 gnu 组织开发,保持了对 sh shell 的兼容性,是各种 Linux 发行版默认配置的 shell。

bash 兼容 sh:
针对 sh 编写的 shell 代码可以不加修改地在 bash 中运行

bash 和 sh 不同:
bash 扩展了一些命令和参数
bash 并不完全和 sh 兼容,它们有些行为并不一致文章来源地址https://www.toymoban.com/news/detail-572203.html

到了这里,关于Rust学习-构建命令行程序的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Rust程序语言设计 第十二章 一个 I/O 项目:构建一个命令行程序

    本章既是一个目前所学的很多技能的概括,也是一个更多标准库功能的探索。我们将构建一个与文件和命令行输入/输出交互的命令行工具来练习现在一些你已经掌握的 Rust 技能。 Rust 的运行速度、安全性、单二进制文件输出和跨平台支持使其成为创建命令行程序的绝佳选择,

    2024年02月13日
    浏览(38)
  • rust 使用第三方库构建mini命令行工具

    这是上一篇 rust 学习 - 构建 mini 命令行工具的续作,扩展增加一些 crate 库。这些基础库在以后的编程工作中会常用到,他们作为基架存在于项目中,解决项目中的某个问题。 项目示例还是以上一篇的工程为基础做调整修改ifun-grep 仓库地址 在开发 ifun-grep 项目时,运行项目命

    2024年02月09日
    浏览(56)
  • rust学习-构建服务器

    服务器会依次处理每一个请求,在完成第一个连接的处理之前不会处理第二个连接 404.html thread::spawn,期望获取一些一旦创建线程就应该执行的代码 但是对于线程池不适用,线程池是当在需要时才执行代码执行 (1)定义 Worker 结构体存放 id 和 JoinHandle() (2)修改 ThreadPool 存

    2024年02月15日
    浏览(36)
  • Rust 程序设计语言学习——基础语法

    Rust 语言是一种高效、可靠的通用高级语言。其高效不仅限于开发效率,它的执行效率也是令人称赞的,是一种少有的兼顾开发效率和执行效率的语言。 Rust 语言由 Mozilla 开发,最早发布于 2014 年 9 月。Rust 的编译器是在 MIT License 和 Apache License 2.0 双重协议声明下的免费开源软

    2024年01月22日
    浏览(50)
  • 【Rust 基础篇】Rust Cargo 自定义构建

    在 Rust 中,Cargo 是一个功能强大的构建工具和包管理器,它可以帮助我们管理项目的依赖、构建和发布。Cargo 提供了许多默认的构建行为,但有时我们需要自定义构建过程以满足特定的需求。本篇博客将详细介绍如何在 Rust 中使用 Cargo 自定义构建过程,包括自定义构建脚本、

    2024年02月16日
    浏览(40)
  • 六个为Rust构建的IDE

    Rust语言的学习曲线适中,介于高级语言和低级语言之间。这门语言既能编写系统软件,将嵌入式设备编译为x86 ARM,也可以用于前端技术,这要归功于WebAssembly。 在日渐成熟的发展中,Rust开始拥有更好的工具来提高效率。最近,一家提供集成开发环境(IDE)的捷克软件公司

    2024年02月22日
    浏览(38)
  • 【Rust】——采用发布配置自定义构建

    💻博主现有专栏:                 C51单片机(STC89C516),c语言,c++,离散数学,算法设计与分析,数据结构,Python,Java基础,MySQL,linux,基于HTML5的网页设计及应用,Rust(官方文档重点总结),jQuery,前端vue.js,Javaweb开发,Python机器学习等 🥏主页链接:     

    2024年04月16日
    浏览(28)
  • Qt6和Rust结合构建桌面应用

    桌面应用程序是原生的、快速的、安全的,并提供Web应用程序无法比拟的体验。 Rust 是一种低级静态类型多范式编程语言,专注于安全性和性能,解决了 C/C++ 长期以来一直在努力解决的问题,例如内存错误和构建并发程序。 在桌面应用程序开发中使用的所有编程语言中,R

    2024年02月11日
    浏览(35)
  • Rust Vs Go:从头构建一个web服务

    Go 和 Rust 之间的许多比较都强调它们在语法和初始学习曲线上的差异。然而,最终的决定性因素是重要项目的易用性。 Rust vs Go 是一个不断出现的话题,并且已经有很多关于它的文章。部分原因是开发人员正在寻找信息来帮助他们决定下一个 Web 项目使用哪种语言,而这两种

    2024年02月22日
    浏览(50)
  • Rust 构建开源 Pingora 框架可以与nginx媲美

    Cloudflare 为何弃用 Nginx,选择使用 Rust 重新构建新的代理 Pingora 框架。Cloudflare 成立于2010年,是一家领先的云服务提供商,专注于内容分发网络(CDN)和分布式域名解析。它提供一系列安全和性能优化服务,包括防火墙、DDoS防护、SSL/TLS加密和威胁分析。 Pingora 是一个基于 R

    2024年03月15日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包