最初的程序,从环境变量获取查询字符串和文件名,读取文件内容
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
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);
}
以上程序存在以下问题
二进制项目需要关注的分离
将解析方法抽象出来
fn main() {
let args: Vec<String> = env::args().collect();
let (query, filename) = parse_config(&args);
// --snip--
}
fn parse_config(args: &[String]) -> (&str, &str) {
let query = &args[1];
let filename = &args[2];
(query, filename)
}
抽离结构体
struct Config {
query: String,
filename: String,
}
fn parse_config(args: &[String]) -> Config {
// clone存在性能问题,之后会优化
let query = args[1].clone();
let filename = args[2].clone();
Config { query, filename }
}
在构造函数中创建结构体
// 将参数封装为结构体
struct Config {
query: String,
filename: String,
}
impl Config {
// 构造函数生成config
fn new(args: &[String]) -> Result<Config, &str> {
if args.len() < 3 {
return Err("not enough arguments");
}
let query = args[1].clone();
let filename = args[2].clone();
Ok(Config { query, filename })
}
}
错误处理,使用restult
而非panic
fn main() {
let args: Vec<String> = env::args().collect();
// let (query, filename) = match Config::new(&args) {
// Ok(config) => (config.query, config.filename),
// Err(err) => panic!("{}",err),
// };
// 闭包处理错误
let config = Config::new(&args).unwrap_or_else(|err| {
println!("{}", err);
process::exit(1);
});
println!("Find {} from {}", config.query, config.filename);
let contents = fs::read_to_string(&config.filename).expect("Input a wrong path of the file.");
println!("File content is: \n{}", contents);
}
将主要逻辑从main中抽离,并进行错误处理
fn main() {
let args: Vec<String> = env::args().collect();
let config = Config::new(&args).unwrap_or_else(|err| {
println!("{}", err);
process::exit(1);
});
// 在main中处理错误,通过if let简化控制流,注意是一个=符号
if let Err(e) = run(config) {
println!("Application error: {}", e);
process::exit(1);
}
}
// Box 意味着函数返回实现了 Error trait 的类型,无需指定具体将会返回的值的类型
fn run(config: Config) -> Result<(), Box<dyn Error>> {
// 使用?替代expect
let contents = fs::read_to_string(&config.filename)?;
println!("file content is: \n{}", contents);
Ok(())
}
将代码分拆到create,将非main函数移动到src/lib.rs
// src/lib.rs
use std::{error::Error, fs};
// 导出函数和config
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
let contents = fs::read_to_string(&config.filename)?;
println!("file content is: \n{}", contents);
Ok(())
}
pub struct Config {
query: String,
filename: String,
}
impl Config {
pub fn new(args: &[String]) -> Result<Config, &str> {
if args.len() < 3 {
return Err("not enough arguments");
}
let query = args[1].clone();
let filename = args[2].clone();
Ok(Config { query, filename })
}
}
// src/main.rs
// main.rs中只剩下了错误处理
use std::env;
use std::process;
use rustdemo::Config;
fn main() {
let args: Vec<String> = env::args().collect();
let config = Config::new(&args).unwrap_or_else(|err| {
println!("{}", err);
process::exit(1);
});
if let Err(e) = rustdemo::run(config){
println!("Application error:{}",e);
process::exit(1);
};
}
测试驱动,之后main函数何以直接调用search
#[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));
}
}
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let mut results = Vec::new();
// lines将分片按照"\n"断行
for line in contents.lines() {
if line.contains(query) {
results.push(line);
}
}
results
}
获取并处理环境变量
use std::env;
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 })
}
}
通过迭代器取代clone
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("CASE_INSENSITIVE").is_err();
Ok(Config { query, filename, case_sensitive })
}
}
// 迭代器的闭包调用
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
contents.lines()
.filter(|line| line.contains(query))
.collect()
}