做移动网站优化排,现在市场最火的网店平台,wordpress的平台,长春企业自助建站系统Rust 的运行速度、安全性、单二进制文件输出和跨平台支持使其成为创建命令行程序的绝佳选择 本文以实现一个minigrep为例#xff0c;展开对之前学习的回归
初版
接收命令行参数并打印文件内容
// 当所需函数嵌套了多于一层模块时#xff0c;通常将父模块引入作用域
// std…Rust 的运行速度、安全性、单二进制文件输出和跨平台支持使其成为创建命令行程序的绝佳选择 本文以实现一个minigrep为例展开对之前学习的回归
初版
接收命令行参数并打印文件内容
// 当所需函数嵌套了多于一层模块时通常将父模块引入作用域
// std::env::args 在其任何参数包含无效 Unicode 字符时会 panic
// 如果需要接受包含无效 Unicode 字符的参数使用 std::env::args_os
// 它返回 OsString 值且OsString 值每个平台都不一样
use std::env;
use std::fs;fn main() {// env::args()返回一个传递给程序的命令行参数的 迭代器iteratorlet args: VecString env::args().collect();// 程序的名称占据了 vector 的第一个值 args[0]和C的命令行参数相同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);
}问题
1main 进行了两个任务函数功能不单一 2query 和 filename 是程序中的配置变量和代码中其他变量混到一起 3打开文件失败使用 expect 来打印出错误信息没有得到失败原因 4使用 expect 来处理不同的错误如果用户没有指定足够的参数来运行程序则展示的错误依旧无法让使用者阅读
解决方式-关注分离
main的职责 1使用参数值调用命令行解析逻辑 2设置任何其他的配置 3调用 lib.rs 中的 run 函数 4如果 run 返回错误则处理这个错误
main.rs 处理程序运行 lib.rs 处理所有的真正的任务逻辑 因为不能直接测试 main 函数这个结构通过将所有的程序逻辑移动到 lib.rs 的函数中使得可以测试他们
重构
重构参数读取
方案一
use std::env;
use std::fs;fn main() {let args: VecString 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,
}// 定义 Config 来包含拥有所有权的 String 值
// main 中的 args 变量是参数值的所有者并只允许 parse_config 函数借用他们
// 意味着如果 Config 尝试获取 args 中值的所有权将违反 Rust 的借用规则
fn parse_config(args: [String]) - Config {// 由于其运行时消耗尽量避免使用 clone 来解决所有权问题let query args[1].clone();let filename args[2].clone();Config { query, filename }
}更合理的参数读取
use std::env;
use std::fs;fn main() {// args类型是alloc::vec::Vecalloc::string::Stringlet args: VecString 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 {// args类型是 [alloc::string::String]// 使用这个方式也可以fn new(args: VecString) - Config// 此时argos类型为alloc::vec::Vecalloc::string::String// 这种转换有待后续深挖fn new(args: [String]) - Config {let query args[1].clone();let filename args[2].clone();Config { query, filename }}
}改善错误信息
执行 cargo run test直接panic
thread main panicked at index out of bounds: the len is 2 but the index is 2, src/main.rs:31:24
note: run with RUST_BACKTRACE1 environment variable to display a backtrace糟糕的方案一
fn new(args: [String]) - Config {if args.len() 3 {panic!(not enough arguments); // 不友好}......// panic信息如下
// thread main panicked at not enough arguments, src/main.rs:26:13
}方案二
返回一个 Result 成功时带有一个 Config 实例 出错时带有一个 static str字符串字面量
use std::env;
use std::fs;
use std::process;struct Config {query: String,filename: String,
}impl Config {fn new(args: [String]) - ResultConfig, static str {if args.len() 3 {return Err(not enough arguments);}let query args[1].clone();let filename args[2].clone();Ok(Config { query, filename })}
}fn main() {let args: VecString env::args().collect();// unwrap_or_else 定义于标准库的 ResultT, E // 使用它可以进行一些自定义的非 panic! 的错误处理// 当其值是 Err 时该方法会调用一个 闭包closure即匿名函数// unwrap_or_else 会将 Err 的内部值即静态字符串传递给|err|let config Config::new(args).unwrap_or_else(|err| {println!(Problem parsing arguments: {}, err);// process::exit 会立即停止程序并将传递给它的数字作为退出状态码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);
}// 打印信息如下
// Problem parsing arguments: not enough argumentsmain 函数处理 new 函数返回的 Result 值 并在出现错误的情况更明确的结束进程
精简main
main的修改
fn main() {// --snip--println!(Searching for {}, config.query);println!(In file {}, config.filename);// 其他处理逻辑全部放入run函数// if let 来检查 run 是否返回一个 Err 值// run 并不返回像 Config::new 返回的 Config 实例那样需要 unwrap 的值// 因为 run 在成功时返回 ()// 而只关心检测错误所以并不需要 unwrap_or_else 来返回未封装的值// 因为它只会是 ()if let Err(e) run(config) {println!(Application error: {}, e);process::exit(1);}
}run的处理
// 引入 trait 对象 Boxdyn Error的路径
use std::error::Error;
// --snip--// unit 类型 ()作为 Ok 时的返回值类型
// trait 对象 Boxdyn Error
// 返回实现了 Error trait 的类型无需指定具体将会返回的值的类型
// 因为在不同的错误场景可能有不同类型的错误返回值
fn run(config: Config) - Result(), Boxdyn Error {// 不同于遇到错误就 panic!// ? 会从函数中返回错误值并让调用者来处理它let contents fs::read_to_string(config.filename)?;println!(With text:\n{}, contents);// 调用 run 函数只是为了它的副作用函数并没有返回什么有意义的值Ok(())
}拆模块
拥有可以测试的公有 API 的库 crate 逻辑提取到了 src/lib.rs 所有的参数解析和错误处理留在了 src/main.rs
直接使用多种参数调用函数并检查返回值无需从命令行运行二进制文件
// src/lib.rs
use std::error::Error;
use std::fs;pub struct Config {pub query: String,pub filename: String,
}impl Config {pub fn new(args: [String]) - ResultConfig, static str {if args.len() 3 {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(), Boxdyn Error {println!(Searching for {}, config.query);println!(In file {}, config.filename);// 不同于遇到错误就 panic!// ? 会从函数中返回错误值并让调用者来处理它let contents fs::read_to_string(config.filename)?;println!(With text:\n{}, contents);// 调用 run 函数只是为了它的副作用函数并没有返回什么有意义的值Ok(())
}// main.rs
use std::env;
use std::process;use rust_minigrep::Config;fn main() {let args: VecString env::args().collect();// unwrap_or_else 定义于标准库的 ResultT, E// 使用它可以进行一些自定义的非 panic! 的错误处理// 当其值是 Err 时该方法会调用一个 闭包closure即匿名函数// unwrap_or_else 会将 Err 的内部值即静态字符串传递给|err|let config Config::new(args).unwrap_or_else(|err| {println!(Problem parsing arguments: {}, err);// process::exit 会立即停止程序并将传递给它的数字作为退出状态码process::exit(1);});if let Err(e) rust_minigrep::run(config) {println!(Application error: {}, e);process::exit(1);}
}TDD开发搜索功能
测试驱动开发Test Driven Development, TDD
use std::error::Error;
use std::fs;pub struct Config {pub query: String,pub filename: String,
}impl Config {pub fn new(args: [String]) - ResultConfig, static str {if args.len() 3 {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(), Boxdyn Error {println!(Searching for {}, config.query);println!(In file {}, config.filename);// 不同于遇到错误就 panic!// ? 会从函数中返回错误值并让调用者来处理它let contents fs::read_to_string(config.filename)?;println!(With text:\n{}, contents);// 调用 run 函数只是为了它的副作用函数并没有返回什么有意义的值Ok(())
}// 在 search 的签名中定义一个显式生命周期 a 并用于 contents 参数和返回值
// 告诉 Rust 函数 search 返回的数据将与 search 函数中的参数 contents 的数据存在的一样久
// 为了使这个引用有效那么 被 slice 引用的数据也需要保持有效
pub fn searcha(query: str, contents: a str) - Veca str {vec![]
}// 如下则编译失败
// Rust 不可能知道需要的是哪一个参数所以需要明确告诉它
// 参数 contents 包含了所有的文本而且希望返回匹配的那部分文本所以contents 应该要使用生命周期语法来与返回值相关联的参数
// 其他语言中并不需要你在函数签名中将参数与返回值相关联
// pub fn search(query: str, contents: str) - Vecstr {// 先编写测试用例
#[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));}
}上述铁定测试失败因为没有开发search模块所以search的功能如下 1遍历内容的每一行文本 2查看这一行是否包含要搜索的字符串 3如果有将这一行加入列表返回值中 4如果没有什么也不做。 5返回匹配到的结果列表
pub fn searcha(query: str, contents: a str) - Veca str {let mut results Vec::new();// contents.lines 返回一个迭代器for line in contents.lines() {// 字符串的contains方法检查包含操作if line.contains(query) {results.push(line);}}results
}最终lib.rs内容如下
use std::error::Error;
use std::fs;pub struct Config {pub query: String,pub filename: String,
}impl Config {pub fn new(args: [String]) - ResultConfig, static str {if args.len() 3 {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(), Boxdyn Error {println!(Searching for {}, config.query);println!(In file {}, config.filename);// 不同于遇到错误就 panic!// ? 会从函数中返回错误值并让调用者来处理它let contents fs::read_to_string(config.filename)?;println!(With text:\n{}, contents);for line in search(config.query, contents) {println!({}, line);}// 调用 run 函数只是为了它的副作用函数并没有返回什么有意义的值Ok(())
}pub fn searcha(query: str, contents: a str) - Veca str {let mut results Vec::new();for line in contents.lines() {if line.contains(query) {results.push(line);}}results
}#[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));}
}错误信息打印到标准输出流
目前为止将所有的输出都 println! 到了终端 大部分终端都提供了两种输出 标准输出standard outputstdout对应一般信息 标准错误standard errorstderr则用于错误信息
cargo run output.txt shell将所有信息存储到 output.txt 结果output.txt中存储了错误信息
将错误打印到标准错误
// 标准库提供了 eprintln! 宏来打印到标准错误流eprintln!(Problem parsing arguments: {}, err);添加区分大小写的功能
设置环境变量来设置搜索是否是大小写敏感
vi ~/.zshrc
添加如下一行
export RUST_CASE_INSENSITIVE1env
查看是否设置成功
RUST_CASE_INSENSITIVE1use std::error::Error;
use std::fs;
use std::env;pub struct Config {pub query: String,pub filename: String,pub case_sensitive: bool, // 新增加控制字段
}impl Config {pub fn new(args: [String]) - ResultConfig, static str {if args.len() 3 {return Err(not enough arguments);}let query args[1].clone();let filename args[2].clone();// 处理环境变量的函数位于标准库的 env 模块中let case_sensitive env::var(RUST_CASE_INSENSITIVE).is_err();Ok(Config { query, filename, case_sensitive })}
}pub fn run(config: Config) - Result(), Boxdyn Error {let contents fs::read_to_string(config.filename)?;let results if config.case_sensitive {search(config.query, contents)} else {search_case_insensitive(config.query, contents)};for line in results {println!({}, line);}Ok(())
}pub fn search_case_insensitivea(query: str, contents: a str) - Veca str {let query query.to_lowercase();let mut results Vec::new();for line in contents.lines() {if line.to_lowercase().contains(query) {results.push(line);}}results
}pub fn searcha(query: str, contents: a str) - Veca str {let mut results Vec::new();for line in contents.lines() {if line.contains(query) {results.push(line);}}results
}#[cfg(test)]
mod tests {use super::*;#[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));}
}使用迭代器重构
// main.rs
use std::env;
use std::process;use rust_minigrep::Config;fn main() {// unwrap_or_else 定义于标准库的 ResultT, E// 使用它可以进行一些自定义的非 panic! 的错误处理// 当其值是 Err 时该方法会调用一个 闭包closure即匿名函数// unwrap_or_else 会将 Err 的内部值即静态字符串传递给|err|let config Config::new(env::args()).unwrap_or_else(|err| {println!(Problem parsing arguments: {}, err);// process::exit 会立即停止程序并将传递给它的数字作为退出状态码process::exit(1);});if let Err(e) rust_minigrep::run(config) {println!(Application error: {}, e);process::exit(1);}
}// lib.rsuse std::error::Error;
use std::fs;
use std::env;pub struct Config {pub query: String,pub filename: String,pub case_sensitive: bool, // 新增加控制字段
}impl Config {pub fn new(mut args: std::env::Args) - ResultConfig, static str {args.next();let query match args.next() {Some(arg) arg,None return Err(Didnt get a query string),};let filename match args.next() {Some(arg) arg,None return Err(Didnt get a file name),};let case_sensitive env::var(RUST_CASE_INSENSITIVE).is_err();println!(query{}, query);println!(filename{}, filename);println!(case_sensitive{}, case_sensitive);Ok(Config { query, filename, case_sensitive })}
}pub fn run(config: Config) - Result(), Boxdyn Error {let contents fs::read_to_string(config.filename)?;let results if config.case_sensitive {println!(run case senstive);search(config.query, contents)} else {println!(run case insenstive);search_case_insensitive(config.query, contents)};for line in results {println!(搜索结果: {}, line);}Ok(())
}pub fn search_case_insensitivea(query: str, contents: a str) - Veca str {let query query.to_lowercase();let mut results Vec::new();for line in contents.lines() {if line.to_lowercase().contains(query) {results.push(line);}}results
}pub fn searcha(query: str, contents: a str) - Veca str {// 字符串数组的迭代器contents.lines()// 留下true的去掉false的.filter(|line| line.contains(query))// 收集结果.collect()
}#[cfg(test)]
mod tests {use super::*;#[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));}
}附录
打印类型
fn print_type_ofT(_: T) {println!({}, std::any::type_name::T())
}环境变量的设置
vi ~/.bash_profile
vi ~/.zshrc需要设置哪个
echo $SHELL系统安装了哪些shell
cat /etc/shellsmac 中使用 zsh部分因为 oh-my-zsh 配置集兼容 bash还能自动补全。 sh 是 unix 上的标准 shell很多 unix 版本都配有它 bash由 gnu 组织开发保持了对 sh shell 的兼容性是各种 Linux 发行版默认配置的 shell。
bash 兼容 sh 针对 sh 编写的 shell 代码可以不加修改地在 bash 中运行
bash 和 sh 不同 bash 扩展了一些命令和参数 bash 并不完全和 sh 兼容它们有些行为并不一致