一.接受运行参数
为了可以接受运行是的参数,需要使用std::env::args
// 可通过collect函数将读取的参数转化为Vec
// cargo run -- test file.txt
// -- 后面的传递给程序的参数
// 第一个参数是默认是工程名字
// test file.txt 才是我们需要的参数
let args: Vec<String> = env::args().collect();
println!("args{:?}", args);
1.2 存储参数
目前已经可以接受参数,参数存放到了args中,下一步我们定义一个结构体保存参数,当前程序需要两个参数,第一个是搜索的字符串,第二个是文件名字,我们可以使用两种方式定义:
// 直接使用自字符串
struct Config {
grep: String,
file_name: String,
}
// 使用引用,注意要标注生存期
struct Config<'a> {
grep: &'a str,
file_name: &'a str,
}
1.3 构造函数定义
有了数据结构,我们可以使用构造函数接受数据参数构造:
// 使用引用时
impl<'a> Config<'a> {
// 静态生存期
fn new(command: &[String]) -> Result<Config, &'static str> {
if command.len() < 3 {
return Err("not enough arguments");
}
Ok(Config {
grep: &command[1],
file_name: &command[2],
})
}
}
// 入参是引用借用关系,无法转移变量,因此需要copy
impl Config {
// 静态生存期
fn new(command: &[String]) -> Result<Config, &'static str> {
if command.len() < 3 {
return Err("not enough arguments");
}
Ok(Config {
grep: command[1].clone(),
file_name: command[2].clone(),
})
}
}
返回Result是为了方便调用方处理异常,错误信息为静态字符串,因此可以使用静态的生存期。
use std::env;
use std::process;
use std::error::Error;
fn main{
// cargo run -- grep file.txt
let args: Vec<String> = env::args().collect();
// Result 的unwrap_or_else方法可以处理异常信息
let config = Config::new(&args).unwrap_or_else(|e| {
print!("error: {e}");
// 退出程序
process::exit(1);
});
println!("query: {}, path: {}!", config.grep, config.file_name);
}
二.读取文件
2.1 新建文件
新建一个txt文件放到根目录,文件内容如下:
I'm nobody! Who are you?
Are you nobody, too?
Then there's a pair of us - don't tell!
They'd banish us, you know.
How dreary to be somebody!
How public, like a frog
To tell your name the livelong day
To an admiring bog!
2.2 读取文件
读取文件需要使用std::fs::read_to_string
#[test]
fn test_run() {
let contents = fs::read_to_string("file.txt").expect("need a file");
print!("contents:\n {:}", contents);
}
2.3 封装文件读取函数
// dyn dynamic缩写,代表错误类型是动态的
// ? 操作符是语法糖,返回值为Result<T,E>的函数,rust会自动转化,发生错误时自动结束返回
fn run(config: Config) -> Result<(), Box<dyn Error>> {
// 语法糖
let contents = fs::read_to_string(config.file_name)?;
print!("contents:\n {}", contents);
Ok(())
}
main函数:
use std::env;
use std::process;
use std::error::Error;
use std::fs;
fn main() {
// 获得运行参数
// cargo run -- grep file.txt
let args: Vec<String> = env::args().collect();
let config = Config::new(&args).unwrap_or_else(|e| {
print!("error: {e}");
process::exit(1);
});
println!("query: {}, path: {}!", config.grep, config.file_name);
if let Err(e) = run(config) {
print!("error: {e}");
process::exit(1);
}
}
struct Config {
grep: String,
file_name: String,
}
// 引用需要标记生存期
impl Config {
// 静态生存期
fn new(command: &[String]) -> Result<Config, &'static str> {
if command.len() < 3 {
return Err("not enough arguments");
}
Ok(Config {
grep: command[1].clone(),
file_name: command[2].clone(),
})
}
}
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
// 语法糖
let contents = fs::read_to_string(config.file_name)?;
print!("contents:\n {}", contents);
Ok(())
}
三. 代码整合
上述完成了对参数的存储,文件的读取,但是有写代码经验的同学,可能会将这些方法封装成模块暴露出来使用,对程序稍作修改即可:
新建lib.rs到src目录:
use std::error::Error;
use std::fs;
pub struct Config<'a> {
pub grep: &'a str,
pub file_name: &'a str,
}
// 引用需要标记生存期
impl<'a> Config<'a> {
// 静态生存期
pub fn new(command: &[String]) -> Result<Config, &'static str> {
if command.len() < 3 {
return Err("not enough arguments");
}
Ok(Config {
grep: &command[1],
file_name: &command[2],
})
}
}
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
// 语法糖
let contents = fs::read_to_string(config.file_name)?;
print!("contents:\n {}", contents);
Ok(())
}
main函数稍作修改:
use std::env;
use std::process;
fn main() {
// 获得运行参数
// cargo run -- grep file.txt
let args: Vec<String> = env::args().collect();
// 这样在main函数可以使用crate::引用
let config = minigrep::Config::new(&args).unwrap_or_else(|e| {
print!("error: {e}");
process::exit(1);
});
println!("query: {}, path: {}!", config.grep, config.file_name);
if let Err(e) = minigrep::run(config) {
print!("error: {e}");
process::exit(1);
}
}
四.搜索字符串
定义一个query方法:
// 需要指定生命周期,因为返回值编译器无法推断和哪个入参生命周期一致
fn search<'a>(query: &'a str, contents: &'a str) -> Vec<&'a str> {
let mut values = Vec::new();
// 使用contains方法检查query是否被包含
for line in contents.lines() {
if line.contains(query) {
values.push(line);
}
}
values
}
lib.rs修改入参为引用:
use std::error::Error;
use std::fs;
pub struct Config<'a> {
pub grep: &'a str,
pub file_name: &'a str,
}
// 引用需要标记生存期
impl<'a> Config<'a> {
// 静态生存期
pub fn new(command: &[String]) -> Result<Config, &'static str> {
if command.len() < 3 {
return Err("not enough arguments");
}
Ok(Config {
grep: &command[1],
file_name: &command[2],
})
}
}
pub fn run(config: &Config) -> Result<String, Box<dyn Error>> {
// 语法糖
let contents = fs::read_to_string(config.file_name)?;
Ok(contents)
}
main函数调用:
use std::env;
use std::process;
fn main() {
// 获得运行参数
// cargo run -- grep file.txt
let args: Vec<String> = env::args().collect();
let config = minigrep::Config::new(&args).unwrap_or_else(|e| {
print!("error: {e}");
process::exit(1);
});
println!("query: {}, path: {}!", config.grep, config.file_name);
let contents = minigrep::run(&config);
if let Err(e) = contents {
print!("error: {e}");
process::exit(1);
}
let contents = contents.unwrap();
let vec = search(&config.grep, &contents);
for line in vec {
println!("{line}");
}
}
fn search<'a>(query: &'a str, contents: &'a str) -> Vec<&'a str> {
let mut values = Vec::new();
for line in contents.lines() {
if line.contains(query) {
values.push(line);
}
}
values
}