当前位置: 首页 > news >正文

趣味学RUST基础篇(构建一个命令行程序2重构)

太好了!我们的探测器已经能听懂指令了,知道要找“藏宝图在此”,并且知道去“地图.txt”里找。但问题来了——它还不会看书

就像一个拿着探测器的探险家,眼睛一闭,啥也看不见。所以,第二步,我们要教它如何“翻阅卷轴”——读取文件!

找个“练习卷轴”

首先,我们得找个“练习卷轴”来教它。就用这首有趣的诗吧!在你的项目文件夹里创建一个叫 poem.txt 的文件,写上这首诗:

“I’m nobody! Who are you?”

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!

(这首诗讲的是“做个无名小卒多自在”,和我们低调寻宝的主题莫名契合!)

给探测器装上“阅读模块”

现在,打开我们的“探测器设计图”(src/main.rs),给它加点“知识”:

use std::env;
use std::fs; // 引入“阅读模块”!就像给机器人装上眼睛

然后,在它听懂指令之后,我们让它去“翻卷轴”:

// ...(前面的代码不变)...println!("正在寻找:{}", query);
println!("在文件中:{}", file_path);//  “翻卷轴”指令!
let contents = fs::read_to_string(file_path).expect("哎呀,卷轴打不开!是不是被施了魔法?");// 让它复述一遍卷轴内容,证明它真的读了!
println!("卷轴上写着:\n{}", contents);

试试“阅读”功能!

现在,运行一下探测器,假装我们要找诗里的单词“the”(虽然还没做搜索功能,先看看它会不会读):

$ cargo run -- the poem.txt

如果一切顺利,你会看到:

正在寻找:the
在文件中:poem.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!

成功了! 我们的探测器不仅能听,还能读!它把整首诗都“看”了一遍,一字不差地复述出来了!

但是…(小问题预警)

虽然现在探测器能读诗了,但它有点“懒”,把整首诗都背下来了。我们真正想要的,是它能精准地找出包含“藏宝图在此”的那一行,而不是把整个卷轴都念一遍。

而且,如果卷轴是湿的、被虫蛀了或者根本不存在,我们的探测器只会说“哎呀,打不开!”,然后直接罢工,这可不行。别担心,这些问题我们接下来就会解决!我们要让它变得更聪明、更健壮!

给你的“寻宝探测器”做个“大手术”!

我们的探测器现在能听指令、能读卷轴,看起来很厉害了。但就像一个刚组装好的机器人,它还有点“笨”:

  1. 它太“贪心”main 函数啥都干,又是听指令,又是读文件,像个“管家婆”。
  2. 它太“乱”query(找啥)和 file_path(在哪找)像两个散落的零件,没有组织。
  3. 它太“暴躁”:文件打不开?直接“死机”(panic),只会说“打不开文件”,像个没耐心的孩子。
  4. 它太“傲娇”:你少给一个参数,它就“发脾气”(panic),也不告诉你为啥。

是时候给它做个“大手术”,也就是重构代码,让它变得更聪明、更专业了!

手术第一步:给机器人“分科”(关注点分离)

医生说:“别让一个函数干所有活!我们来分工!”

  • src/main.rs:变成“总指挥室”。它只负责三件事:
    1. 听指令(收参数)。
    2. 把指令交给“专家部门”(lib.rs)。
    3. 如果专家说“搞不定”,就优雅地关机。
  • src/lib.rs:变成“专家实验室”。所有复杂的逻辑,比如解析指令、读文件、找宝藏,都放这里。这里的东西还能被“测试”,确保它靠谱!

手术第二步:把零件“装盒子”(创建 Config 结构体)

之前,queryfile_path 就像两个散落的螺丝钉。现在,我们给它们造一个“工具盒”——Config 结构体!

struct Config {query: String,      // 要找的“关键词”file_path: String,  // 要搜的“文件”
}

现在,main 函数只要拿着这个“工具盒”(config)就能干活,再也不用记住两个变量名了,整洁多了!

小插曲:为啥要用 .clone()
想象 args 是一份“原始指令书”,main 是保管员。Config 想要一份副本,但不能直接拿走原件(借用规则)。最简单的方法就是复印一份(.clone())。虽然复印费点时间,但胜在简单直接!等以后学了“高级复印术”(生命周期),我们再优化。

手术第三步:让“专家”来解析(Config::new)

我们把解析参数的活,从 main 里拿出来,交给 Config 自己的“工厂”——new 函数。

impl Config {fn new(args: &[String]) -> Config {let query = args[1].clone();let file_path = args[2].clone();Config { query, file_path }}
}

现在,main 只需说:“Config::new(指令书),给我一个工具盒!”,简单明了。

手术第四步:让它“会说话”,别“发脾气”(错误处理升级)

之前,如果参数不够,机器人就直接“死机”:

thread 'main' panicked at 'index out of bounds: the len is 1 but the index is 1'

用户看了只会一脸懵。现在,我们让它学会“礼貌地报告故障”:

  1. 检查故障:在 Config::new 里加个检查:
    if args.len() < 3 {return Err("兄弟,参数不够啊!要‘找啥’和‘在哪找’两个才行!"); // 不再 panic!
    }
    
  2. 返回“诊断报告”new 函数现在返回 Result<Config, 错误信息>,就像医生的诊断单:Ok(工具盒)Err(错误信息)
  3. “总指挥”优雅处理main 收到“诊断单”后,如果是 Err,就打印友好的提示,然后关机:
    let config = Config::new(&args).unwrap_or_else(|err| {println!("问题:{}", err);process::exit(1); // 用非零状态码关机,表示出错了
    });
    

现在,如果用户输错命令:

$ cargo run
问题:兄弟,参数不够啊!要‘找啥’和‘在哪找’两个才行!

是不是瞬间感觉专业多了?

手术第五步:把“寻宝逻辑”也搬进实验室

读文件、找关键词的活,也不能让“总指挥”干。我们把它搬到 lib.rs,变成一个叫 run 的“寻宝专家”函数:

pub fn run(config: Config) -> Result<(), Box<dyn Error>> {let contents = fs::read_to_string(config.file_path)?; // ? 运算符,出错直接返回println!("卷轴内容:\n{}", contents);Ok(())
}

main 只需呼叫专家:

if let Err(e) = run(config) {println!("寻宝失败:{}", e);process::exit(1);
}

手术成功!

经过这一系列“升级手术”,我们的“寻宝探测器”焕然一新:

  • 结构清晰:指挥室(main.rs)和实验室(lib.rs)分工明确。
  • 沟通顺畅:用 Result 传递“诊断报告”,不再无脑“死机”。
  • 易于维护:所有核心逻辑在 lib.rs,以后加新功能或改代码,都不会影响“总指挥”。
  • 便于测试lib.rs 里的函数都可以写测试用例来验证!

现在,它已经不再是玩具,而是一个专业级的探险工具了!

完整代码

//main.rsrust
use std::{env, process};
use minigrep::{run, Config};fn main() {let args: Vec<String> = env::args().collect();let config = Config::new(&args).unwrap_or_else(|err|{println!("问题:{}", err);process::exit(1);});if let Err(e) = run(config){println!("应用程序出错 :{e}");process::exit(1);}}
//lib.rs
use std::error::Error;
use std::fs;pub fn run(config: Config) ->Result<(), Box<dyn Error>>{let contents = fs::read_to_string(config.file_path)?;println!("卷轴内容:\n{contents}");Ok(())
}pub struct Config {query: String,file_path: String,
}impl Config {pub fn new(args: &[String]) -> Result<Config, &'static str> {if args.len()<3 {panic!("兄弟,参数不够啊!要‘找啥’和‘在哪找’两个才行!");}let query = args[1].clone();let file_path = args[2].clone();Ok(Config { query, file_path })}
}
http://www.xdnf.cn/news/1462627.html

相关文章:

  • 基于FPGA实现数字QAM调制系统
  • AiPPT生成的PPT内容质量怎么样?会不会出现逻辑混乱或数据错误?
  • 一键生成PPT的AI工具排名:2025年能读懂你思路的AI演示工具
  • 深度学习——迁移学习
  • 鸿蒙:获取UIContext实例的方法
  • Spring Boot+Nacos+MySQL微服务问题排查指南
  • 国产化PDF处理控件Spire.PDF教程:如何在 Java 中通过模板生成 PDF
  • 抓虫:sw架构防火墙服务启动失败 Unable to initialize Netlink socket: 不支持的协议
  • 还有人没搞懂住宅代理IP的属性优势吗?
  • java解析网络大端、小端解析方法
  • 信息安全基础知识
  • 云原生部署_Docker入门
  • 将 Android 设备的所有系统日志(包括内核日志、系统服务日志等)完整拷贝到 Windows 本地
  • android View详解—动画
  • Kali搭建sqli-labs靶场
  • modbus_tcp和modbus_rtu对比移植AT-socket,modbus_tcp杂记
  • 《sklearn机器学习——聚类性能指数》同质性,完整性和 V-measure
  • 从 Prompt 到 Context:LLM OS 时代的核心工程范式演进
  • [特殊字符] AI时代依然不可或缺:精通后端开发的10个GitHub宝藏仓库
  • Xilinx系列FPGA实现DP1.4视频收发,支持4K60帧分辨率,提供2套工程源码和技术支持
  • 【Arxiv 2025 预发行论文】重磅突破!STAR-DSSA 模块横空出世:显著性+拓扑双重加持,小目标、大场景统统拿下!
  • K8S的Pod为什么可以解析访问集群之外的域名地址
  • LeetCode刷题-top100( 矩阵置零)
  • android 四大组件—BroadcastReceiver
  • 《深入理解双向链表:增删改查及销毁操作》
  • 贪吃蛇鱼小游戏抖音快手微信小程序看广告流量主开源
  • 架构性能优化三板斧:从10秒响应到毫秒级的演进之路
  • VSCode+MobaXterm+X11可视化界面本地显示
  • pydantic定义llm response数据模型
  • A股大盘数据-20250905 分析