Rust编程语言入门教程课程笔记
project: minigrep
src/main.rs
//grep: globally search a regular expression and print
use std::env;//command line arguments
use std::process;//exit
use minigrep::Config;//Config struct
use minigrep::run;//run function
//Separation of Concerns for Binary Projects
//Splitting code into a main.rs and a lib.rs is a good default choice when starting a binary project.
//1. Split your program into a main.rs and a lib.rs and move your program’s logic to lib.rs.
//2. As long as your command line parsing logic is small, it can remain in main.rs.
//3. When the command line parsing logic starts getting complicated, extract it from main.rs and move it to lib.rs.
fn main() {
let args: Vec<String> = env::args().collect();//collect command line arguments
// println!("{:?}", args);//print command line arguments //[./target/debug/minigrep, xxxx, yyyy]
// let query = &args[1];//query string
// let filename = &args[2];//filename
//let (query, filename) = parse_config(&args[1..]);//parse command line arguments
// let config = parse_config(&args);//parse command line arguments
// let config = Config::new(&args);//parse command line arguments
let config = Config::build(&args).unwrap_or_else(|err| {
// println!("Problem parsing arguments: {}", err);
eprintln!("Problem parsing arguments: {}", err);//error handling: print to stderr
process::exit(1);
});//parse command line arguments
// println!("Searching for {}", query);
// println!("In file {}", filename);
// let contents = fs::read_to_string(config.filename)
// .expect("Something went wrong reading the file");//read file
// println!("With text:\n{}", contents);
if let Err(e) = run(config){
// println!("Application error: {}", e);
eprintln!("Application error: {}", e);//error handling: print to stderr
process::exit(1);
}
}
lib.rs
use std::fs;//file system
use std::error::Error;//error handling
use std::env;//environment variables
// fn parse_config(args: &[String]) -> (&str, &str) {
// let query = &args[1];//query string
// let filename = &args[2];//filename
// (query, filename)
// }
pub fn run(config: Config) -> Result<(), Box<dyn Error>>{
let contents = fs::read_to_string(config.filename)?;
//.expect("Something went wrong reading the file");//read file
//println!("With text:\n{}", contents);
let results = if config.case_sensitive {//if case sensitive
search(&config.query, &contents)//search case sensitive
} else {
search_case_insensitive(&config.query, &contents)//search case insensitive
};
// for line in search(&config.query, &contents) {//iterate over each line
// println!("{}", line);//print line
// }
for line in results {//iterate over each line
println!("{}", line);//print line
}
Ok(())
}
pub struct Config {
query: String,
filename: String,
case_sensitive: bool,
}
// fn parse_config(args: &[String]) -> Config {
// let query = args[1].clone();//query string
// let filename = args[2].clone();//filename
// Config { query, filename }
// }
impl Config {
// fn new(args: &[String]) -> Config {
// if args.len() < 3 {
// panic!("not enough arguments");
// }
// let query = args[1].clone();//query string
// let filename = args[2].clone();//filename
// Config { query, filename }
// }
pub fn build(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();//case sensitive
Ok(Config { query, filename, case_sensitive })
}
}
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {//<'a> lifetime annotation
// let mut results = Vec::new();//mutable vector
// for line in contents.lines() {//iterate over each line
// if line.contains(query) {//if line contains query
// results.push(line);//add line to results
// }
// }
// results//return results
contents.lines()//iterate over each line
.filter(|line| line.contains(query))//if line contains query
.collect()//collect into vector
}
pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {//<'a> lifetime annotation
let mut results = Vec::new();//mutable vector
let query = query.to_lowercase();//convert query to lowercase
for line in contents.lines() {//iterate over each line
if line.to_lowercase().contains(&query) {//if line contains query
results.push(line);//add line to results
}
}
results//return results
}
//TDD: Test-Driven Development
//Writing a Failing Test and Seeing It Pass
//1. Write a test that fails and run it to make sure it fails for the reason you expect.
//2. Write or modify just enough code to make the new test pass.
//3. Refactor the code you just added or changed and make sure the tests continue to pass.
//4. Repeat from step 1!
#[cfg(test)]
mod tests {
use super::*;//import outer scope
#[test]
fn one_result() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Pick three.";
assert_eq!(
vec!["safe, fast, productive."],
search(query, contents)
);
}
#[test]
fn case_sensitive() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";
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)
);
}
}