【跟小嘉学 Rust 编程】一、Rust 编程基础
【跟小嘉学 Rust 编程】二、Rust 包管理工具使用
【跟小嘉学 Rust 编程】三、Rust 的基本程序概念
【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念
【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据
【跟小嘉学 Rust 编程】六、枚举和模式匹配
【跟小嘉学 Rust 编程】七、使用包(Packages)、单元包(Crates)和模块(Module)来管理项目
【跟小嘉学 Rust 编程】八、常见的集合
【跟小嘉学 Rust 编程】九、错误处理(Error Handling)
【跟小嘉学 Rust 编程】十一、编写自动化测试
【跟小嘉学 Rust 编程】十二、构建一个命令行程序
本章是一个目前所学的很多技能的应用,以及标准库的探索,我们讲构建一个命令行程序工具来练习现在已经学习过的一些Rust的技能。我们将构建自己的版本的命令行工具:grep(Globally search a Regular Expression and print)。
主要教材参考 《The Rust Programming Language》
$ cargo new minigrep
Created binary (application) `minigrep` project
$ cd minigrep
minigrep 能够接受两个命令行参数:文件名和要搜索的字符串。也就是说我们希望使用cargo run的时候,可以使用如下的方式。
cargo run searchstring example-filename.txt
在 Crates.io 上会有一些现场的库帮助我们接受命令行参数(clap)。不过我们现阶段使用标准库。
为了能够接受命令行参数的值,我们需要使用 rust 标准库提供的函数。该函数返回一个命令行参数的迭代器(iiterator),迭代器我们将会在下一章详细讲解。我们只需要知道在迭代器上有一个方法 collect 可以将其转换为一个集合。
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
println!("{:?}", args);
}
需要注意 args 函数 在其任何参数包含 无效Unicode 字符时会panic。 如果你需要接受包含无效Unicode字符的参数,使用 std::env::args_os
代替。该函数返回 OsString值而不是 String 值。
Vector 的第一个参数是二进制文件的名称。
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);
}
use std::{env, fs};
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);
}
fs::read_to_string(filename)
方法打开文件,返回包含内容的Result
。
我们上述代码 main 函数有着多个职责,通常函数只负责一个功能会更加简洁并且易于维护。在开发的时候重构是一个最佳时间,重构少量代码要容易的多。
我们最初的代码存在下面四个问题:
index out of bounds
错误,而这并不能明确解释问题。如果所有的错误处理都位于一处,这样将来的维护者需要在修改错误处理逻辑时只需要考虑这一处代码。main 函数负责多个任务的组织问题在许多二进制项目中很常见。所以 Rust 社区开发出一类在 main 函数开始变得庞大时进行二进制程序的关注分离的指导性过程。这些过程有如下步骤:
经过这些过程之后保留在 main 函数中的责任应该被限制为:
这个模式的一切就是为了关注分离:main.rs 处理程序运行,而 lib.rs 处理所有的真正的任务逻辑。因为不能直接测试 main 函数,这个结构通过将所有的程序逻辑移动到 lib.rs 的函数中使得我们可以测试他们。仅仅保留在 main.rs 中的代码将足够小以便阅读就可以验证其正确性。让我们遵循这些步骤来重构程序
use std::{env, fs};
fn main() {
let args: Vec<String> = env::args().collect();
let (query,filename) = parse_config(&args);
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 parse_config(args: &[String]) -> (&str, &str) {
let query = &args[1];
let filename = &args[2];
(query, filename)
}
use std::{env, fs};
fn main() {
let args: Vec<String> = env::args().collect();
let config = parse_config(&args);
println!("Searching for {}", config.query);
println!("In file {}", config.filename);
let contents = fs::read_to_string(config.filename)
.expect("Something 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 }
}
我们需要注意 我们定义的 Config 包含拥有所有权的String值,我们返回来引用 args 中的 String值的字符串切片 slice。 main函数的args变量是参数值的所有者并只允许 parse_config 方法借用他们。这意味着 Config 尝试获取args 中的值的所有权将违反 Rust的借用规则。
还有许多不同的方式可以处理 String 的数据,而最简单但有些不太高效的方式是调用这些值的 clone 方法。这会生成 Config 实例可以拥有的数据的完整拷贝,不过会比储存字符串数据的引用消耗更多的时间和内存。不过拷贝数据使得代码显得更加直白因为无需管理引用的生命周期,所以在这种情况下牺牲一小部分性能来换取简洁性的取舍是值得的。
由于其运行时消耗,许多 Rustacean 之间有一个趋势是倾向于避免使用 clone 来解决所有权问题。
在关于迭代器的章节中,我们将学习如何更加有效率的处理这种情况,不过现在复制字符串取得进展是没有问题的。因为只会进行一次这样的拷贝,而且文件名和要搜索的字符串都比较短。
use std::{env, fs};
fn main() {
let args: Vec<String> = env::args().collect();
let config = Config::new(&args);
println!("Searching for {}", config.query);
println!("In file {}", config.filename);
let contents = fs::read_to_string(config.filename)
.expect("Something 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 }
}
}
对于错误,我们可以使用 panic!,但是 panic!更趋向于程序上的问题,而不是使用上的问题,我们应该使用Result 枚举来处理错误。
use std::{env, fs, process};
const ARGS_LENGTH:usize= 3;
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);
});
println!("Searching for {}", config.query);
println!("In file {}", config.filename);
let contents = fs::read_to_string(config.filename)
.expect("Something 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() < ARGS_LENGTH {
return Err("not enough arguments")
}
let query = args[1].clone();
let filename = args[2].clone();
Ok( Config { query, filename })
}
}
use std::{env, fs, process};
const ARGS_LENGTH:usize= 3;
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);
});
println!("Searching for {}", config.query);
println!("In file {}", config.filename);
run(config);
}
struct Config {
query: String,
filename: String,
}
impl Config {
fn new(args: &[String])-> Result<Config, &'static str>{
if args.len() < ARGS_LENGTH {
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("Something went wrong reading the file");
println!("With text:\n{}", contents);
}
use std::{env, fs, process, error::Error};
const ARGS_LENGTH:usize= 3;
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);
});
println!("Searching for {}", config.query);
println!("In file {}", config.filename);
if let Err(e) = run(config) {
println!(" Application error: {}", e);
process::exit(1);
}
}
struct Config {
query: String,
filename: String,
}
impl Config {
fn new(args: &[String])-> Result<Config, &'static str>{
if args.len() < ARGS_LENGTH {
return Err("not enough arguments")
}
let query = args[1].clone();
let filename = args[2].clone();
Ok( Config { query, filename })
}
}
fn run(config: Config) -> Result<(), Box<dyn Error>>{
let contents = fs::read_to_string(config.filename)?;
println!("With text:\n{}", contents);
Ok(())
}
use std::{fs, error::Error};
const ARGS_LENGTH:usize= 3;
pub struct Config {
pub query: String,
pub filename: String,
}
impl Config {
pub fn new(args: &[String])-> Result<Config, &'static str>{
if args.len() < ARGS_LENGTH {
return Err("not enough arguments")
}
let query = args[1].clone();
let filename = args[2].clone();
Ok( Config { query, filename })
}
}
pub 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, process};
use minigrep::Config;
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);
});
println!("Searching for {}", config.query);
println!("In file {}", config.filename);
if let Err(e) = minigrep::run(config) {
println!(" Application error: {}", e);
process::exit(1);
}
}
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let mut result = Vec::new();
for line in contents.lines(){
if line.contains(query) {
result.push(line);
}
}
println!("{:?}", result);
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn one_result() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Pick, three.
Duct tape.";
assert_eq!(
vec!["safe, fast, productive."],
search(query, contents)
);
}
}
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(())
}
use std::{fs, error::Error, env};
const ARGS_LENGTH:usize= 3;
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() < ARGS_LENGTH {
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)?;
for line in search(&config.query, &contents, config.case_sensitive) {
println!("{}", line);
}
Ok(())
}
pub fn search<'a>(query: &str, contents: &'a str, case_sensitive: bool) -> Vec<&'a str> {
let mut result = Vec::new();
if case_sensitive {
let query_ignore_sensitive = query.to_lowercase();
for line in contents.lines(){
if line.to_lowercase().contains(&query_ignore_sensitive) {
result.push(line);
}
}
return result;
} else {
for line in contents.lines(){
if line.contains(&query) {
result.push(line);
}
}
return result;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn one_result() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Pick, three.
Duct tape.";
assert_eq!(
vec!["safe, fast, productive."],
search(query, contents)
);
}
}
println!() 宏就是把输出信息输出到标准输出
eprintln!() 宏就是把输出信息输出到标准错误
use std::{env, process};
use minigrep::Config;
fn main() {
let args: Vec<String> = env::args().collect();
let config = Config::new(&args).unwrap_or_else(|err|{
eprintln!("Problem parsing arguments:{}", err);
process::exit(1);
});
println!("Searching for {}", config.query);
println!("In file {}", config.filename);
println!("case_sensitive: {}", config.case_sensitive);
if let Err(e) = minigrep::run(config) {
eprintln!(" Application error: {}", e);
process::exit(1);
}
}
以上就是今天要讲的内容