【Rust学习】12. 项目实战-搜索关键词

参考:https://www.bilibili.com/video/BV1hp4y1k7SV
模仿grep写一个小项目

12.1 接受命令行参数

  1. 创建项目
cargo new minigrep
  1. 要在终端输入接受命令行参数,希望用户有如下输入
cargo run XXX XXXXX.txt
use std::env;
fn main() {
    let args:Vec<String>=env::args().collect();
    println!("{:?}",args);
}

输出

cargo run 
["target\\debug\\minigrep.exe"]
$ cargo run hello myfile.txt
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target\debug\minigrep.exe hello myfile.txt`
["target\\debug\\minigrep.exe", "hello", "myfile.txt"]

但是env::args0无法处理非法字符,会发生恐慌
TODO: 可以考虑用env::args_os()来解决,此处暂不考虑

  1. 接受两个参数并保存
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);
}
$ cargo run hello myfile.txt
   Compiling minigrep v0.1.0 (D:\Workplace\Rust\study\minigrep)
    Finished dev [unoptimized + debuginfo] target(s) in 0.14s
     Running `target\debug\minigrep.exe hello myfile.txt`
["target\\debug\\minigrep.exe", "hello", "myfile.txt"]
Search for 'hello'
In file myfile.txt

12.2 读取文件

use std::env;
use std::fs;//处理和文件相关的任务
fn main() {
    let args:Vec<String>=env::args().collect();
    let query =&args[1];
    let filename=&args[2];

    println!("Search for '{}'",query);
    println!("In file {}",filename);

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

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

$ cargo run abc story.txt
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target\debug\minigrep.exe abc story.txt`
Search for 'abc'
In file story.txt
With text:
Working from home has become increasingly common, but it can be a challenge to stay productive in this environment.
To maintain focus, it is important to establish a designated workspace that is free of distractions.
It is also helpful to set specific work hours and take regular breaks throughout the day to stay energized.
Minimizing distractions such as social media notifications or TV shows can help increase productivity.
Finally, staying connected with colleagues through video conferencing tools can prevent feelings of isolation.
By implementing these tips, individuals can stay productive while working from home and maintain a healthy work-life balance.

12.3 重构:改进模块

存在问题:

  • 主函数负责的任务太多了
  • 程序代码最好一个函数负责一个功能
  • 字段太多太杂难以管理–创建struct管理
  • 错误打印的信息不清晰错误处理不好,难以维护,放置一处处理好

二进制程序关注点分离的指导性原则

  • 将程序拆分为 main.rs 和 librs,将业务逻辑放入 lib.rs
  • 当命令行解析逻辑较少时,将它放在main.rs也ok
  • 当命令行解析逻辑变复杂时,需要将它从 main.rs 提取到lib.rs
    main负责运行
    lib负责处理业务逻辑

经过上述拆分,留在 main 的功能有:

  • 使用参数值调用命令行解析逻辑
  • 进行其它配置
  • 调用lib.rs中的run函数
  • 处理run函数可能出现的错误
fn main() {
    let args:Vec<String>=env::args().collect();
    
    let (query,filename)=parse_config(&args);
...
}
fn parse_config(args:&[String])->(&str,&str){
    let query=&args[1];
    let filename=&args[2];
    (query,filename)
}

但是两个配置参数放在一个元组里,没有关联
-----放在一个struct里面

use std::env;
use std::fs;//处理和文件相关的任务
fn main() {
    let args:Vec<String>=env::args().collect();

    let config=parse_config(&args);
    let contents=fs::read_to_string(config.filename)
    .expect("Someing went wrong reading the file");

    println!("With text:\n{}",contents);
}
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 }
}

clone虽然低效,但是简洁性强
把函数和config相关联

use std::env;
use std::fs;//处理和文件相关的任务
fn main() {
    let args:Vec<String>=env::args().collect();

    let config=Config::new(&args);
    let contents=fs::read_to_string(config.filename)
    .expect("Someing went wrong reading the file");

    println!("With text:\n{}",contents);
}
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 }
    }
}

12.3 重构:错误处理

$ cargo run
warning: field `query` is never read
  --> src\main.rs:13:5
   |
12 | struct Config{
   |        ------ field in this struct
13 |     query:String,
   |     ^^^^^
   |
   = note: `#[warn(dead_code)]` on by default

warning: `minigrep` (bin "minigrep") generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target\debug\minigrep.exe`
thread 'main' panicked at 'index out of bounds: the len is 1 but the index is 1', src\main.rs:21:19
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `target\debug\minigrep.exe` (exit code: 101)

对于用户输入的参数个数处理

struct Config{
    query:String,
    filename:String,
}
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 }
    }
}

$ cargo run x
warning: field `query` is never read
  --> src\main.rs:13:5
   |
12 | struct Config{
   |        ------ field in this struct
13 |     query:String,
   |     ^^^^^
   |
   = note: `#[warn(dead_code)]` on by default

warning: `minigrep` (bin "minigrep") generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 0.04s
     Running `target\debug\minigrep.exe x`
thread 'main' panicked at 'not enough arguments', src\main.rs:19:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `target\debug\minigrep.exe x` (exit code: 101)

修改返回

use std::env;
use std::fs;//处理和文件相关的任务
use std::process;

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

    println!("With text:\n{}",contents);
}
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 })
    }
}

TODO:有报错,还没解决
更新,把参数换成0即可

$ cargo run x
   Compiling minigrep v0.1.0 (D:\Workplace\Rust\study\minigrep)
warning: field `query` is never read
  --> src\main.rs:20:5
   |
19 | struct Config{
   |        ------ field in this struct
20 |     query:String,
   |     ^^^^^
   |
   = note: `#[warn(dead_code)]` on by default

warning: `minigrep` (bin "minigrep") generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 0.37s
     Running `target\debug\minigrep.exe x`
Problem parsing arguments:not enough arguments
error: process didn't exit successfully: `target\debug\minigrep.exe x` (exit code: 1)

12.3 重构:把业务逻辑移动到lib.rs

封装读取文件的代码

use std::env;
use std::fs;//处理和文件相关的任务
use std::process;

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);//终止程序
        }
    );
    run(config);
}
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 run(config:Config){
    let contents=fs::read_to_string(config.filename)
    .expect("Someing went wrong reading the file");

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

修改

fn run(config:Config)->Result<(),Box<dyn Error>>{
    let contents=fs::read_to_string(config.filename)?;
    println!("With text:\n{}",contents);
    Ok(())
}

提示要加处理错误的代码
在这里插入图片描述
修改

use std::env;
use std::error::Error;
use std::fs;//处理和文件相关的任务
use std::process;

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);
    }
}
fn run(config:Config)->Result<(),Box<dyn Error>>{
    let contents=fs::read_to_string(config.filename)?;
    println!("With text:\n{}",contents);
    Ok(())
}
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 })
    }
}

代码分离,将业务逻辑代码移动到lib.rs
main.rs

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

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

lib.rs

use std::error::Error;
use std::fs;//处理和文件相关的任务

pub fn run(config:Config)->Result<(),Box<dyn Error>>{
    let contents=fs::read_to_string(config.filename)?;
    println!("With text:\n{}",contents);
    Ok(())
}
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 })
    }
}

12.4 使用TDD(测试驱动开发)开发库功能

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

  • 编写一个会失败的测试,运行该测试,确保它是按照预期的原因失败
  • 编写或修改刚好足够的代码,让新测试通过
  • 重构刚刚添加或修改的代码,确保测试会始终通过
  • 返回步骤1,继续

开发搜索关键词的功能

/**
 * 搜索关键词
 */
pub fn search<'a>(query:&str,contents:&'a str)->Vec<&'a str>{
    vec![]
}
// TDD测试开发
#[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));
    }
}
$ cargo test
warning: unused variable: `query`
  --> src\lib.rs:30:19
   |
30 | pub fn search<'a>(query:&str,contents:&'a str)->Vec<&'a str>{
   |                   ^^^^^ help: if this is intentional, prefix it with an underscore: `_query`
   |
   = note: `#[warn(unused_variables)]` on by default

warning: unused variable: `contents`
  --> src\lib.rs:30:30
   |
30 | pub fn search<'a>(query:&str,contents:&'a str)->Vec<&'a str>{
   |                              ^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_contents`

warning: `minigrep` (lib) generated 2 warnings (run `cargo fix --lib -p minigrep` to apply 2 suggestions)
warning: `minigrep` (lib test) generated 2 warnings (2 duplicates)
   Compiling minigrep v0.1.0 (D:\Workplace\Rust\study\minigrep)
    Finished test [unoptimized + debuginfo] target(s) in 0.29s
     Running unittests src\lib.rs (target\debug\deps\minigrep-74a78b83f1e9ac19.exe)

running 1 test
test tests::one_result ... FAILED

failures:

---- tests::one_result stdout ----
thread 'tests::one_result' panicked at 'assertion failed: `(left == right)`
  left: `["safe, fast, productive."]`,
 right: `[]`', src\lib.rs:51:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::one_result

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

补充代码

/**
 * 搜索关键词
 */
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
}
$ cargo test
   Compiling minigrep v0.1.0 (D:\Workplace\Rust\study\minigrep)
    Finished test [unoptimized + debuginfo] target(s) in 0.33s
     Running unittests src\lib.rs (target\debug\deps\minigrep-74a78b83f1e9ac19.exe)

running 1 test
test tests::one_result ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src\main.rs (target\debug\deps\minigrep-da816c566c541e67.exe)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests minigrep

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

/**
 * 读取文件
 */
pub fn run(config:Config)->Result<(),Box<dyn Error>>{
    let contents=fs::read_to_string(config.filename)?;
    for line in search(&config.query, &contents){
        println!("{}",line);
    }
    Ok(())
}
$ cargo run help story.txt
   Compiling minigrep v0.1.0 (D:\Workplace\Rust\study\minigrep)
    Finished dev [unoptimized + debuginfo] target(s) in 0.37s
     Running `target\debug\minigrep.exe help story.txt`
It is also helpful to set specific work hours and take regular breaks throughout the day to stay energized.
Minimizing distractions such as social media notifications or TV shows can help increase productivity.

12.5 环境变量

// TDD测试开发
#[cfg(test)]
mod tests{
    use super::*;
    /**
     * 大小写敏感
     */
    #[test]
    fn case_sensitive(){
        let query="duct";
        let contents="\
Rust:
safe, fast, productive.
Pick three.";
        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));
        }

}
/**
 * 大小写敏感搜索
 */
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
}
/**
 * 大小写不敏感搜索
 */
pub fn search_case_insensitive<'a>(query:&str,contents:&'a str)->Vec<&'a str>{
    let mut results=Vec::new();
    let query=query.to_lowercase();
    for line in contents.lines(){
        if line.to_lowercase(). contains(&query){
            results.push(line);
        }
    }
    results
}
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();
        let case_sensitive=env::var("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<'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
}
/**
 * 大小写不敏感搜索
 */
pub fn search_case_insensitive<'a>(query:&str,contents:&'a str)->Vec<&'a str>{
    let mut results=Vec::new();
    let query=query.to_lowercase();
    for line in contents.lines(){
        if line.to_lowercase(). contains(&query){
            results.push(line);
        }
    }
    results
}
// TDD测试开发
#[cfg(test)]
mod tests{
    use super::*;
    /**
     * 大小写敏感
     */
    #[test]
    fn case_sensitive(){
        let query="duct";
        let contents="\
Rust:
safe, fast, productive.
Pick three.";
        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));
        }

}
$ cargo run Rust story.txt
    Finished dev [unoptimized + debuginfo] target(s) in 0.03s
     Running `target\debug\minigrep.exe Rust story.txt`
Rust:

$ CASE_INSENSITIVE=1 cargo run Rust story.txt
    Finished dev [unoptimized + debuginfo] target(s) in 0.03s
     Running `target\debug\minigrep.exe Rust story.txt`
Rust:
Trust me.";


12.6将错误信息写入标准错误而不是标准输出

标准输出 VS 标准错误

  • 标准输出: stdout
    • println!
  • 标准错误: stderr
    • eprintln!

在这里插入图片描述

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

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

    let config=Config::new(&args).unwrap_or_else(|err|
        {
            // println!("Problem parsing arguments:{}",err);
            eprintln!("Problem parsing arguments:{}",err);
            process::exit(1);//终止程序
        }
    );
    if let Err(e)=minigrep::run(config){
        // println!("Application error:{}",e);
        eprintln!("Application error:{}",e);
        process::exit(1);
    }
}

$ cargo run Rust story.txt > output.txt
    Finished dev [unoptimized + debuginfo] target(s) in 0.03s
     Running `target\debug\minigrep.exe Rust story.txt`

在这里插入图片描述
完整代码:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值