参考:https://www.bilibili.com/video/BV1hp4y1k7SV
模仿grep写一个小项目
12.1 接受命令行参数
- 创建项目
cargo new minigrep
- 要在终端输入接受命令行参数,希望用户有如下输入
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()来解决,此处暂不考虑
- 接受两个参数并保存
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`
完整代码: