接受命令行参数
主要:实现在特定文件中搜索特定的内容
use std::env;
//args函数会返回一个迭代器,迭代器会产生一系列的值
//我们可以调用collect方法把这些值转化成一个集合
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);
}
加入读取文件
use std::env;
//args函数会返回一个迭代器,迭代器会产生一系列的值
//我们可以调用collect方法把这些值转化成一个集合
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("Something wrong in reading txt");
println!("with the text:\n{}",contents);
}
但是这样一股脑的将功能全部放在main函数里会导致可读性变差,可维护性变弱,因此rust社区发行了二进制关注点分离的指导性原则。
1、将程序拆分为main.rs和lib.rs时,将业务逻辑放进lib.rs。
2、当命令行解析逻辑较少时,将他放在main.rs也可以。
3、当命令行解析逻辑变复杂时,需要将他从main.rs提取到lib.rs。
改善模块化
我们尝试将解析参数的代码解剖出来
use std::env;
//args函数会返回一个迭代器,迭代器会产生一系列的值
//我们可以调用collect方法把这些值转化成一个集合
use std::fs;
//处理跟文件相关的一些事物
fn main() {
let args :Vec<String> = env::args().collect();
let (query, filename) = parse_config(&args);
let contents = fs::read_to_string(filename).expect("Something wrong in reading txt");
println!("with the text:\n{}",contents);
}
fn parse_config(args: &[String]) ->(&str, &str) {
//通过这个函数来解析参数
let query = &args[1];
let filename = &args[2];
(query,filename)
}
这段代码的可读性还不够强,那么我们继续做改进
use std::env;
//args函数会返回一个迭代器,迭代器会产生一系列的值
//我们可以调用collect方法把这些值转化成一个集合
use std::fs;
//处理跟文件相关的一些事物
fn main() {
let args :Vec<String> = env::args().collect();//读取了一个字符串,args的所有权归main函数
let config = Config::new(&args);
let contents = fs::read_to_string(config.filename).expect("Something wrong in reading txt");
println!("with the 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{
//这里是需要arg中某些切片的所有权的,所以我们采用克隆的方法获得原切片的副本
query,
filename,
}
}
}
错误处理
代码总是会发生错误,这里我们对可能发生的错误进行处理。
use std::env;
//args函数会返回一个迭代器,迭代器会产生一系列的值
//我们可以调用collect方法把这些值转化成一个集合
use std::fs;
//处理跟文件相关的一些事物
use std::process;
fn main() {
let args :Vec<String> = env::args().collect();//读取了一个字符串,args的所有权归main函数
let config = Config::new(&args).unwrap_or_else(|err|{
//如果发生了错误,会屏蔽掉之前的一些代码然后打印err中的内容
println!("problem parsing arguments: {}", err);
process::exit(1);
});
let contents = fs::read_to_string(config.filename).expect("Something wrong in reading txt");
println!("with the text:\n{}",contents);
}
struct Config{
query:String,
filename:String,
}
impl Config {
fn new(args: &[String]) ->Result<Config, &'static str> {
//传了一个切片进去,但是目前并没有所有权
//如果出现错误就返回一个err变体
if args.len() < 3{
return Err("not enough arguments")
}
let query = args[1].clone();
let filename = args[2].clone();
Ok( Config{
//这里是需要arg中某些切片的所有权的,所以我们采用克隆的方法获得原切片的副本
query,
filename,
})
}
}
将业务逻辑移动至lib.rs
main.rs中代码如下:
use minigrep::Config;
use std::env;
//args函数会返回一个迭代器,迭代器会产生一系列的值
//我们可以调用collect方法把这些值转化成一个集合
use std::process;
fn main() {
let args :Vec<String> = env::args().collect();//读取了一个字符串,args的所有权归main函数
let config = Config::new(&args).unwrap_or_else(|err|{
//如果发生了错误,会屏蔽掉之前的一些代码然后打印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 struct Config{
pub query:String,
pub filename:String,
}
pub fn run(config:Config) ->Result<(),Box<dyn Error>>{
let contents = fs::read_to_string(config.filename)?;//在发生错误时不会产生panic而是把错误交给函数的调用者来进行处理
println!("with the text:\n{}",contents);
Ok(())
}
impl Config {
pub fn new(args: &[String]) ->Result<Config, &'static str> {
//传了一个切片进去,但是目前并没有所有权
//如果出现错误就返回一个err变体
if args.len() < 3{
return Err("not enough arguments")
}
let query = args[1].clone();
let filename = args[2].clone();
Ok( Config{
//这里是需要arg中某些切片的所有权的,所以我们采用克隆的方法获得原切片的副本
query,
filename,
})
}
}
使用TDD编写库功能
编写一个会失败的测试,运行该测试,确保他按照预期的原因失败
编写或修改刚好足够的代码,让新测试通过
重构刚刚添加或修改的代码,确保测试会通过
返回步骤1,继续
main.rs函数不做修改
use minigrep::Config;
use std::env;
//args函数会返回一个迭代器,迭代器会产生一系列的值
//我们可以调用collect方法把这些值转化成一个集合
use std::process;
fn main() {
let args :Vec<String> = env::args().collect();//读取了一个字符串,args的所有权归main函数
let config = Config::new(&args).unwrap_or_else(|err|{
//如果发生了错误,会屏蔽掉之前的一些代码然后打印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 struct Config{
pub query:String,
pub filename:String,
}
pub fn run(config:Config) ->Result<(),Box<dyn Error>>{
let contents = fs::read_to_string(config.filename)?;//在发生错误时不会产生panic而是把错误交给函数的调用者来进行处理
for line in search(&config.query, &contents){
println!("{}",line);
}
println!("with the text:\n{}",contents);
Ok(())
}
impl Config {
pub fn new(args: &[String]) ->Result<Config, &'static str> {
//传了一个切片进去,但是目前并没有所有权
//如果出现错误就返回一个err变体
if args.len() < 3{
return Err("not enough arguments")
}
let query = args[1].clone();
let filename = args[2].clone();
Ok( Config{
//这里是需要arg中某些切片的所有权的,所以我们采用克隆的方法获得原切片的副本
query,
filename,
})
}
}
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) {
//lines.contains()返回的是一个bool类型
results.push(line);
}
}
results
}
#[cfg(test)] //测试函数
mod tests{
use super::*;
#[test]
fn one_result() {
let query = "duct";
let contents = "\
Rust:
safe,fase,productive.
pick three.";
assert_eq!(vec!["safe,fase,productive."],search(query,contents ))
}
}
使用环境变量
用一个环境变量来控制搜索时是否需要将大小写进行区分
CASE_INSENSITIVE=1 : 无法将“CASE_INSENSITIVE=1”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。
main.rs
use minigrep::Config;
use std::env;
//args函数会返回一个迭代器,迭代器会产生一系列的值
//我们可以调用collect方法把这些值转化成一个集合
use std::process;
fn main() {
let args :Vec<String> = env::args().collect();//读取了一个字符串,args的所有权归main函数
let config = Config::new(&args).unwrap_or_else(|err|{
//如果发生了错误,会屏蔽掉之前的一些代码然后打印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;
//处理跟文件相关的一些事物
use std::env;
pub struct Config{
pub query:String,
pub filename:String,
pub case_sensitive:bool,
}
pub fn run(config:Config) ->Result<(),Box<dyn Error>>{
let contents = fs::read_to_string(config.filename)?;//在发生错误时不会产生panic而是把错误交给函数的调用者来进行处理
let results = if config.case_sensitive{
search(&config.query, &contents)
}else {
search_case_insensitive(&config.query, &contents)
};
for line in results{
println!("{}",line);
}
println!("with the text:\n{}",contents);
Ok(())
}
impl Config {
pub fn new(args: &[String]) ->Result<Config, &'static str> {
//传了一个切片进去,但是目前并没有所有权
//如果出现错误就返回一个err变体
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{
//这里是需要arg中某些切片的所有权的,所以我们采用克隆的方法获得原切片的副本
query,
filename,
case_sensitive
})
}
}
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) {
//lines.contains()返回的是一个bool类型
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) {
//lines.contains()返回的是一个bool类型
results.push(line);
}
}
results
}
#[cfg(test)] //测试函数
mod tests{
use super::*;
#[test]
fn case_sensitive() {
let query = "duct";
let contents = "\
Rust:
safe,fase,productive.
pick three.
Duct tape.";
assert_eq!(vec!["safe,fase,productive."],search(query,contents ))
}
#[test]
fn case_insensitive(){
let query = "rUsT";
let contents = "\
Rust:
safe,fase,productive.
pick three.
Trust me.";
assert_eq!(vec!["Rust:","Trust me."],search_case_insensitive(query, contents) )
}
}
将错误信息输出到标准错误
标准错误vs标准输出
标准输出:stdout
—println!
标准错误:stderr
—eprintln!
main.rs进行修改
use minigrep::Config;
use std::env;
//args函数会返回一个迭代器,迭代器会产生一系列的值
//我们可以调用collect方法把这些值转化成一个集合
use std::process;
fn main() {
let args :Vec<String> = env::args().collect();//读取了一个字符串,args的所有权归main函数
let config = Config::new(&args).unwrap_or_else(|err|{
//如果发生了错误,会屏蔽掉之前的一些代码然后打印err中的内容
eprintln!("problem parsing arguments: {}", err);
process::exit(1);
});
if let Err(e) = minigrep::run(config){
//如果返回了一个错误
eprintln!("Application error: {}",e);
process::exit(1);
}
}
cmd输入
cargo run to poem.txt > output.txt
会将输出的内容存放到output.txt文件里