尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

【Rust自学】12.7. 使用环境变量

【Rust自学】12.7. 使用环境变量
📅 发布时间:2026/7/23 19:00:03

12.7 使用环境变量

12.7.0. 写在正文之前

第12章要做一个实例的项目——一个命令行程序。这个程序是一个grep(Global Regular Expression Print),是一个全局正则搜索和输出的工具。它的功能是在指定的文件中搜索出指定的文字。

这个项目分为这么几步:
- 接收命令行参数
- 读取文件
- 重构:改进模块和错误处理
- 使用TDD(测试驱动开发)开发库功能
-使用环境变量(本文)
- 将错误信息写入标准错误而不是标准输出

12.7.1. 回顾

以下是截止到上一篇文章为止所写出的全部代码。

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]) -> Result<Config, &'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<(), Box<dyn Error>> { let contents = fs::read_to_string(config.filename)?; for line in search(&config.query, &contents) { println!("{}", line); } Ok(()) } pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a 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)); } }

main.rs:

use std::env; use std::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); }); if let Err(e) = minigrep::run(config) { println!("Application error: {}", e); process::exit(1); } }

本文,我们将通过添加一个额外的功能来改进minigrep:用户可以通过环境变量打开不区分大小写搜索的选项。我们可以将此功能设置为命令行选项,并要求用户每次希望应用时输入它,但通过将其设置为环境变量,我们允许用户设置环境变量一次,并在那个终端会话中使所有搜索都不区分大小写。

12.7.2. 继续用TDD:不区分大小写搜索

我们再次遵循TDD的步骤:
- 编写一个会失败的测试,运行该测试,并确保它是按照预期的原因失败
- 编写或修改刚好足够的代码,让新测试通过
- 重构刚刚添加或修改的代码,确保测试会通过
- 返回步骤1,继续

本篇文章会继续用TDD来实现不区分大小写的搜索。新函数会被命名为search_case_insensitive,之后再用环境变量来控制是否启用它。

12.7.3. 编写会失败的测试

这个对大小写不敏感的函数先给它起个名叫做search_case_insensitive。

先把测试模块改一下,改出一个对大小写敏感的测试函数和不敏感的测试函数:

#[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) ); } }

然后再写search_case_insensitive函数的具体内容:

pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { vec![] }
  • 为了让这个函数能被外部函数调用,得使用pub来声明为公共的
  • 这个函数得加生命周期标志,因为有多个非self的参数,Rust无法判断哪个参数的生命周期跟返回值的生命周期相同
  • 返回值Vector内的元素是字符串切片,是从contents截取的,所以返回值应和contents的生命周期相同,所以给它们两个标注了一样的生命周期'a,而query则不需要生命周期标注
  • 函数内容只需要确保能通过编译即可,因为TDD的第一步是编写一个会出错的测试,所以出错才是想要的结果

这时候跑测试肯定会失败,但没关系,这正是TDD第一步想要的。

12.7.4. 编写或修改刚好足够的代码,让新测试通过

其实search_case_insensitive的代码与search的大部分都差不多,只需要做一些小修改即可。逻辑很好想,就是把关键词变成小写,再与文本各行的小写形式进行比较:

pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { let mut results = Vec::new(); let query = query.to_lowercase(); for line in contents.lines() { if line.to_lowercase().contains(&query) { results.push(line); } } results }
  • to_lowercase方法可以把字符串变成全小写
  • to_lowercase转换后的结果是String,变量拥有所有权,也就是新的query是String而不是&str。在循环中的if语句使用的是&query,因为contains方法不接受String所以得传引用进去

再跑一下测试:

$ cargo test Compiling minigrep v0.1.0 (/tmp/minigrep) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.15s Running unittests src/lib.rs (target/debug/deps/minigrep-dfdfbb86b622af32) running 2 tests test tests::case_sensitive ... ok test tests::case_insensitive ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Running unittests src/main.rs (target/debug/deps/minigrep-4c31ade9c6771135) running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Doc-tests minigrep running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

两个测试都通过了。

12.7.5. 在run函数中使用此函数

这个函数没问题了,就可以在run函数中调用了。

但是首先得先为Config结构体添加一个字段来作为使用普通的search还是对大小写不敏感的search_case_insensitive的依据:

pub struct Config { pub query: String, pub filename: String, pub case_sensitive: bool, }

修改run函数让它判断配置:

pub fn run(config: Config) -> Result<(), Box<dyn 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(()) }

Config上的new这个构造器也得改,根据环境变量给case_sensitive这个字段赋值:

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 = std::env::var("IGNORE_CASE").is_err(); Ok(Config { query, filename, case_sensitive, }) } }

这里使用了std::env::var这个函数(当然也可以先把std::env导入作用域,再使用env::var),它的参数是这个环境变量的名称(按惯例全大写),这里我写的是IGNORE_CASE,中文翻译过来就是忽略大小写。这种环境变量只要出现就认为不区分大小写,不出现就认为区分大小写。

std::env::var的返回值是Result类型的,如果这个IGNORE_CASE环境变量被设置了,返回包含环境变量值的Ok(String),反之返回Err(std::env::VarError)。

这里std::env::var后面还跟了is_err这个方法:如果返回值是Err变体,is_err就返回true,于是case_sensitive被赋为true;反之(环境变量已设置)则赋false。

PS:说实话,这个小程序写成这个B样也是为了教学的无奈之举,我看到一半我都被这个代码量气笑了,真正写的时候没必要写得这么一板一眼的

12.7.6. 整体代码与试运行

写了这么多,看看截止到目前的所有代码。

lib.rs:

use std::error::Error; use std::fs; 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() < 3 { return Err("not enough arguments"); } let query = args[1].clone(); let filename = args[2].clone(); let case_sensitive = std::env::var("IGNORE_CASE").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)?; 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<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { let mut results = Vec::new(); for line in contents.lines() { if line.contains(query) { results.push(line); } } results } pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { let mut results = Vec::new(); let query = query.to_lowercase(); for line in contents.lines() { if line.to_lowercase().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 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); }); if let Err(e) = minigrep::run(config) { println!("Application error: {}", e); process::exit(1); } }

来试运行一下:

首先,我们将在不设置环境变量的情况下运行程序,并使用查询to,该查询应与包含全部小写单词to的任何行匹配:

$ cargo run -- to poem.txt Compiling minigrep v0.1.0 (/tmp/minigrep) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.07s Running `target/debug/minigrep to poem.txt` Are you nobody, too? How dreary to be somebody!

这次将IGNORE_CASE设置为1,其它不变:

$ IGNORE_CASE=1 cargo run -- to poem.txt

会得到:

Are you nobody, too? How dreary to be somebody! To tell your name the livelong day To an admiring bog!

没有任何问题。

注意,如果你在powershell中,设置环境变量得这么写:

PS> $Env:IGNORE_CASE=1; cargo run -- to poem.txt

这会使这个环境变量在这个会话中一直存在,如果要去掉这个环境变量,写:

PS> Remove-Item Env:IGNORE_CASE

相关新闻

  • 高效办公 AI 智能体搭建,OpenClaw 整合包极简部署方案(含安装包)
  • mysql多条查询结果纵向拼接
  • PSO-XGBoost组合模型在工业预测中的优化与应用

最新新闻

  • 无锡钻戒回收注意事项,别被虚高报价白白套路 - 日常比对手册
  • AI Agent自动化工作流设计:企业智能助手如何落地
  • AI办公不是替代人,而是淘汰不会用AI的人:3类岗位正在消失,4类新角色年薪涨62%
  • 嵌入式系统参数动态更新利器:TI MCU POM模块原理与实战
  • 上海管道疏通避坑指南 虹口区快达师傅上门快不坑(2026年7月) - 余生黄金回收
  • 强强联合,共拓数字化新蓝海|河南花仙子网络与金正集团(经营帮)达成战略合作

日新闻

  • 亨得利盐城维修点在哪里?手表维修保养地址指南**公示(2026年7月最新) - 亨得利官方
  • 提升.NET API安全性:Boxed.AspNetCore.Swagger认证授权最佳实践
  • 帝舵佛山**网点地址更新:2026年7月售后热线电话与服务客户指南 - 帝舵中国官方服务中心

周新闻

  • SaaS软件行业GEO实践:AI搜索时代的品牌可见性与获客新路径
  • 什么是PCTFE?医药高端包装的“防潮王牌“材料
  • 【JVM调优实战】16-可视化利器-JConsole-VisualVM-JMC

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号