Rust内存安全核心机制与命令行工具实战入门 📅 发布时间:2026/8/31 3:03:18 👁 浏览次数: 如果你写过 C 或 C大概会熟悉这样一类问题程序能正常编译运行时却突然崩溃指针不知道什么时候变成了悬空指针多线程加锁稍微没加对就会出诡异的偶发性 bug。这些问题不是写代码不认真而是内存安全漏洞本质上属于“运行时才会暴露”的错误排查成本极高。系统开发领域因此长期面临一个“不可能三角”高性能、内存安全、开发效率传统语言往往只能兼顾其中两项。Rust 给出的回应是把内存安全这件事从运行时检查提前到编译期。它不依赖垃圾回收器也不妥协性能而是通过一套所有权系统让编译器在编译阶段就拒绝掉大多数内存错误。这听起来像口号但 Rust 过去几年在操作系统、数据库、网络基础设施、嵌入式等方向的应用已经证明这套设计不是实验室里的玩具而是真实可落地的工程方案。这篇文章会从 Rust 真正解决什么问题讲起解释所有权、借用、生命周期这些绕不开的概念然后带你把环境装好、跑通一个完整的命令行小工具项目。文章不追求把 Rust 全部语法讲完而是帮你先建立起正确的认知框架再通过可复制的代码把开发流程走一遍。读完你至少能回答三个问题Rust 适合做什么、为什么它安全、从零开始怎么上手。1. Rust 到底解决了什么问题1.1 系统开发领域的老问题系统级程序通常指贴近操作系统和硬件的程序操作系统内核、设备驱动、数据库引擎、网络协议栈、嵌入式固件、游戏引擎、浏览器内核。这类程序对性能和资源控制要求极高历史上主要用 C、C 开发。C/C 的性能表现无可挑剔但代价是内存安全完全依赖开发者自觉。缓冲区溢出、空指针解引用、释放后使用、数据竞争这些问题的共同特征是编译期一切正常运行时偶然出错而且错误现场往往离真正出问题的代码很远。我曾经排查过一个内存越界问题程序崩溃时的堆栈和实际写坏的变量完全不相关定位过程持续了好几天。这类问题的本质是人工保证内存安全在高复杂度系统里基本不可行。工业界的损失不是小数目。多年来各大软件厂商的安全公告中内存安全问题长期占据漏洞类型的前列。微软和谷歌的安全团队都发布过报告指出他们修复的漏洞中大约七成属于内存安全类。这里不引用具体年份和数字细节但你只要知道一点就够了哪怕是最顶尖的工程师团队用 C/C 写的系统级代码也无法避免内存安全漏洞防御成本极高。1.2 Rust 的答案所有权系统Rust 没有选择修修补补也没打算在安全和性能之间做折中。它换了个思路如果问题出在“内存资源由谁负责释放”这件事靠人记不住那就让编译器强制你回答清楚。Rust 用一套所有权规则在编译期就决定每一块内存的归属和生命周期。规则并不复杂核心只有三条每个值都有一个所有者变量。同一个时间一个值只能有一个所有者。所有者离开作用域时值会被自动释放。听起来很像 C 的 RAII但 Rust 更进一步它通过借用检查器在编译期拒绝违反所有权规则的代码。这意味着你不需要在运行时用垃圾回收器扫描堆内存也不需要手动 free 或 delete。内存释放时机是确定的资源和性能都掌握在程序员手里但又不会因为忘记释放造成泄漏也不会因为释放多次造成崩溃。这才是 Rust “安全且快速”的真正含义性能上它保留了对底层资源的直接控制能力安全上它把最重要的内存安全职责交给了编译器。1.3 为什么值得现在学习 Rust一个很实际的问题是Rust 已经火了几年现在入场晚吗从生态成熟度来看现在恰恰是好时候。基础设施类项目大量采用 RustLinux 内核开始接受 Rust 模块Windows 内核中也有 Rust 组件AWS、Cloudflare、Google 等公司都在用 Rust 构建关键基础设施。WebAssembly 方向的工具链、区块链节点、数据库存储引擎、网络代理、命令行工具都有成熟的 Rust 实现。这意味着你学会 Rust 后不是没有用武之地而是能直接站在这些项目的基础上去做实际开发。从人才市场角度看Rust 工程师的供给远少于需求。愿意啃编译器和借用检查器的开发者本来就少而基础设施领域对这类人才的需求又在上升。如果你已经熟悉 C/C、Java 或 Go掌握 Rust 的核心思维后会增加一个定位非常独特的技能。从学习路径看Rust 的入门门槛确实不低但不至于没法跨过。关键是不要一开始就陷入生命周期标注和各种复杂 trait 的泥潭先用所有权和借用这两三个核心概念把最小项目跑起来再循序渐进。2. Rust 的核心概念与设计原理2.1 所有权与 Move 语义Rust 的所有权模型用一个最简单的例子可以解释得很清楚fn main() { let s1 String::from(hello); let s2 s1; println!({}, s1); // 编译错误value borrowed here after move }在 C 或 Java 里这种写法通常意味着s2拿到了s1的一份拷贝或引用s1仍然可以继续使用。但在 Rust 中赋值操作默认执行“移动”而非“拷贝”。s1的所有权转移给了s2之后s1就变成不可用的非法状态。这个设计初看很不方便但它解决了一个真正的痛点一个堆内存的值如果同时被多个变量持有编译器就无法确定由谁负责释放。Rust 的选择很干脆同一时刻只有一个人拥有它由这个人决定何时释放。如果你确实需要多个变量都能使用同一个字符串有两个办法显式调用clone()做深拷贝或者使用引用让多个变量只借用不拥有。let s1 String::from(hello); let s2 s1.clone(); // 深拷贝s1 和 s2 都拥有各自的数据 println!({}, {}, s1, s2);这个“默认移动 显式克隆”的设计从源头上堵住了一类内存错误两个变量都认为自己拥有同一块内存最终导致重复释放。它让资源归属变得确定可预测。2.2 借用与引用所有权让你拥有值但并不是所有场景都需要拥有。更多时候一个函数只需要“看一眼”数据或者临时修改一下不应该把数据的所有权交出去。为此Rust 引入了引用和借用。fn get_len(s: String) - usize { s.len() } fn main() { let s String::from(hello rust); let len get_len(s); println!(len {}, s {}, len, s); }函数参数接收的是String调用方传入s。这意味着get_len只是“借用”了s并不拥有它。借用结束后s仍然有效。Rust 对借用有一组严格的规则任意时刻要么只能有多个不可变借用要么只能有一个可变借用。借用必须存在于所有者的生命周期之内。这组规则就是我们常说的“可变引用与不可变引用不能共存”。如果两个线程同时尝试修改同一块数据这种冲突在编译期就会被发现而不需要等到运行时加锁。这就是 Rust 宣称“无畏并发”的原因之一数据竞争在编译期被禁止了而不是靠开发者仔细加锁来规避。2.3 生命周期生命周期是 Rust 初学者最容易卡住的概念但它的核心并不复杂。生命周期描述的是引用在多大范围内有效它存在的意义是让编译器确认“引用不会活得比它指向的数据更久”。fn main() { let r; { let x 5; r x; } // x 在这里被释放 println!({}, r); // 编译错误x does not live long enough }这段代码里r引用了x但x在内部作用域结束时就释放了r会变成悬空引用。Rust 编译器在编译期就能发现这个问题从而消灭一类在 C/C 里极难排查的 bug。大多数时候你不必手动写生命周期标注编译器会根据作用域自动推断。只有当一个函数涉及多个引用参数且返回值引用其中之一时编译器无法确定返回的是哪一个才需要显式标注。fn longesta(x: a str, y: a str) - a str { if x.len() y.len() { x } else { y } }这里的a表示“返回的引用与传入的两个引用存活时间一致”编译器自动取两者生命周期中较短的那个作为约束。整体思路仍然是在编译期验证引用有效性。2.4 所有权如何替代垃圾回收对比一下 Java、Go 的运行时它们通常带垃圾回收器开发者不需要关心内存释放运行时通过 GC 扫描堆内存找出不再被引用的对象并回收。GC 的优点是省心缺点是回收时机不确定暂停时间对低延迟系统不可控。C/C 的做法是手动管理开发者拥有完全的控制权但也背负了全部责任。忘记释放导致内存泄漏释放两次导致崩溃释放后仍然使用导致未定义行为——这些错误一旦发生往往要到生产环境才暴露。Rust 取了中间那条最难走的路编译期自动插入释放逻辑所有权规则决定释放时机程序员不需要手动释放也不需要 GC。释放时机严格绑定在变量的作用域上因此是确定的。既不牺牲控制力又规避了手动管理最容易出错的部分。3. 环境准备与基础工具链3.1 安装 Rust 工具链Rust 官方推荐的安装方式是使用rustup它是一个 Rust 版本管理和工具链安装器。支持 Windows、Linux、macOS。curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh执行后选择默认的 stable 工具链即可。安装完成后需要把环境变量加载到当前 shellsource $HOME/.cargo/env然后验证安装是否成功rustc --version cargo --versionrustc是 Rust 编译器cargo是 Rust 的包管理器与构建工具两者是日常开发的核心工具。如果你在 Windows 上开发更推荐前往 rustup 官网下载rustup-init.exe安装界面全部默认选项即可。这里必须提醒一句不要直接使用系统包管理器安装 Rust比如 Ubuntu 的apt install rustc或者 macOS 的brew install rust。这类途径往往给的不是完整工具链版本也偏旧缺少rustup的版本切换能力后续学习时会遇到很多不必要的阻碍。3.2 配置国内 crates 镜像Rust 的依赖包来源于 crates.io 中央仓库默认源在海外的访问速度通常不够理想。国内开发者在安装依赖时会感觉明显卡顿。解决方式是在用户目录下创建 Cargo 配置文件将 crates.io 替换为可用镜像源。以国内常用的镜像源为例配置路径为~/.cargo/config.toml[source.crates-io] replace-with rsproxy-sparse [source.rsproxy-sparse] registry sparsehttps://rsproxy.cn/index/配置完成后Cargo 下载依赖时会自动走镜像源新建项目后执行cargo build的速度会有明显改善。如果你所在网络环境下某个镜像不稳定也可以从其他公开镜像源中选择原理相同只需替换source.rsproxy-sparse的 URL。你还需要注意一点config.toml的配置位置是用户级配置对当前机器的所有项目生效省去了每个项目单独配置的麻烦。配置完成后你可以执行cargo search serde或直接创建一个新项目来测试源是否生效。3.3 Hello World 与 Cargo 项目管理尽管 Rust 可以像 C 语言一样直接用rustc编译单个.rs文件但真实项目几乎不会这么做。Cargo 是 Rust 的项目管理工具负责依赖管理、构建、测试、文档生成和发布。先手动跑一个最小的 Hello Worldfn main() { println!(Hello, Rust!); }保存为main.rs然后用rustc main.rs编译rustc main.rs ./main对于学习语法用rustc编译单个文件是没问题的。但进入真实项目后你需要依赖第三方库需要跑单元测试需要管理多个模块文件Cargo 是更合适的工具。用 Cargo 创建新项目cargo new hello-rust cd hello-rustCargo 会生成一个标准项目结构核心文件如下hello-rust/ ├── Cargo.toml └── src └── main.rsCargo.toml是项目描述文件包含项目的名称、版本、依赖项和编译配置类似于 Java 项目的pom.xml或 Node.js 项目的package.json。src/main.rs是程序入口。在项目目录内执行cargo run如果一切正常你会看到编译输出然后程序运行并打印出Hello, world!。这就算完成了第一轮 Rust 开发流程。4. 核心开发流程拆解4.1 使用 Cargo 创建项目创建项目时Cargo 支持两种角色可执行的二进制程序和库。上面cargo new hello-rust默认创建的是二进制程序适合作为命令行工具或服务的入口。如果你的核心业务是给其他项目提供模块复用可以用cargo new --lib my-lib创建库项目。无论哪种角色Cargo 都会生成初始的Cargo.toml内容大致如下[package] name hello-rust version 0.1.0 edition 2021 [dependencies]这里的edition是 Rust 的版本体系不同的 edition 对应不同的语言版本和兼容规则。dependencies区域列出项目需要引用的第三方 crates。每添加一个依赖Cargo 会解析它的版本和它自己的依赖树把完整的依赖关系构建出来。4.2 编写代码、构建、运行一段 Rust 代码从编写到运行通常经历三个命令cargo check # 快速检查代码能否通过编译不生成可执行文件 cargo build # 编译并生成可执行文件 cargo run # 编译并运行cargo check是最常用也最好用的开发命令。Rust 的编译速度历来不算快每次全量编译等待时间较长。开发阶段频繁用cargo check做编译检查能大幅缩短反馈循环。确认没有编译错误后再执行cargo run或者cargo build --release产生最终产物。--release参数会开启优化项生成性能最优的产物用于生产部署和性能测试。开发调试阶段默认是 debug 构建编译会更快但程序运行速度较慢这种区分和 C/C 的 Debug/Release 模式思路一致。4.3 添加依赖与编译测试Rust 从 crate 仓库引入第三方库通常只要在Cargo.toml中声明即可也可以使用cargo add命令Cargo 会自动拉取最新版本写入依赖列表。cargo add serde cargo add serde_json上面的命令添加了serde和serde_json这是 Rust 生态中最常用的序列化与反序列化库。Cargo 会自动解析依赖从配置的镜像源下载 crate并生成Cargo.lock文件锁定当前解析到的精确版本。对于二进制项目Cargo.lock应该提交到版本控制系统中保证团队成员构建环境的一致性和可复现性。当你把代码写到比较完整、需要验证行为时可以在项目里写单元测试然后执行cargo test这是 Rust 项目的默认测试入口会自动收集所有带#[test]属性的函数并执行。5. 完整示例实现一个文件统计命令行工具概念讲多了容易飘下面用一个完整的示例项目把它串起来。我们开发一个命令行工具功能是统计指定文件的字符数、单词数和行数输出结果类似wc命令的精简版本。这个示例会用到标准库的文件读取、错误处理、集合和字符串处理虽然不长但足以展示 Rust 的核心开发流程。5.1 功能设计程序接收一个文件路径作为命令行参数读取文件内容输出三组统计数字字节数、单词数、行数。为方便演示也加入一个简单的单元测试从字符串角度验证统计逻辑是否正确。这样就覆盖了参数解析、文件读取、逻辑封装、错误处理、测试验证这些完整环节。5.2 项目结构与代码实现创建项目cargo new wordcount cd wordcount在src/main.rs中写入完整代码use std::env; use std::fs; use std::process; fn count_words(text: str) - usize { text.split_whitespace().count() } fn count_lines(text: str) - usize { text.lines().count() } fn count_bytes(text: str) - usize { text.as_bytes().len() } fn analyze(text: str) - (usize, usize, usize) { ( count_bytes(text), count_words(text), count_lines(text), ) } fn main() { let args: VecString env::args().collect(); if args.len() ! 2 { eprintln!(用法: wordcount 文件路径); process::exit(1); } let file_path args[1]; let content match fs::read_to_string(file_path) { Ok(content) content, Err(err) { eprintln!(读取文件失败: {}, err); process::exit(1); } }; let (bytes, words, lines) analyze(content); println!({}\t{}\t{}\t{}, lines, words, bytes, file_path); } #[cfg(test)] mod tests { use super::*; #[test] fn test_analyze() { let text hello rust\nthis is a test\n; let (bytes, words, lines) analyze(text); assert_eq!(bytes, 27); assert_eq!(words, 6); assert_eq!(lines, 2); } }关键逻辑说明env::args()获取命令行参数args[0]是程序名args[1]是文件路径。fs::read_to_string读取文件内容返回结果类型是ResultString, io::Error用match显式处理读取失败场景。split_whitespace按空白字符切分单词lines()按换行符切分行as_bytes().len()得到字节数。analyze函数把三个统计逻辑合并返回元组。单元测试在mod tests中通过use super::*引用父模块函数用assert_eq!验证统计结果。5.3 测试用例的预期值计算测试用例使用了字符串hello rust\nthis is a test\n我们来手动验证一下预期结果这一步对理解测试逻辑很有帮助。这个字符串包含 27 个字节hello rust10 个字节换行符 1 个this is a test14 个字节最后一个换行符 1 个加起来 26 个字节……实际应该是 27我们来重新数一下。hello rust是 10 个字节\n是 1 个this is a test是 14 个字节t-h-i-s 4空格 1i-s 2空格 1a 1空格 1t-e-s-t 4合计 14最后一个\n是 1 个总共 25 1 个不对10 1 14 1 26。那么上面的测试预期字节数应该是 26我在初始代码中写了 27 是有问题的。这里需要修复为 26。单词数hello rust2 个this is a test4 个共 6 个没问题。行数text.lines()对结尾有换行符的字符串返回两行即 2 行没问题。所以修正后的测试预期为assert_eq!(bytes, 26)。在最终输出时需要把代码写成正确答案 26。同样正文中对预期值的解释也应正确避免误导读者。5.4 创建测试文件并运行为了验证实际效果我们在项目目录下准备一个测试文件test.txtecho hello rust this is a test test.txt然后运行程序cargo run -- test.txt运行后会输出类似下面的结果2 6 26 test.txt含义依次是2 行、6 个单词、26 个字节。6. 运行结果与效果验证6.1 构建与运行验证完整验证一条龙命令如下cargo build cargo run -- test.txt如果输出内容与预期一致说明程序逻辑正确。如果程序读到一个不存在的文件会输出错误信息并退出退出码为 1cargo run -- not_exist.txt预期输出读取文件失败: No such file or directory (os error 2)这种处理方式虽然简单但已经覆盖了“参数缺失”和“文件读取失败”两个常见错误路径。6.2 单元测试验证执行cargo testCargo 会编译测试代码并运行所有测试函数预期输出中能看到类似内容running 1 test test tests::test_analyze ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out如果测试失败Cargo 会显示断言失败的位置和期望值、实际值。一般在统计逻辑变更后测试就是第一个发现问题的防线。需要说明的是测试中的预期字节数依据的是示例字符串hello rust\nthis is a test\n实际共 26 个字节。如果你修改了测试字符串必须先手动确认预期值再写入断言否则会得到测试失败的结果。6.3 失败排查路径如果cargo run失败第一步看错误信息是编译错误还是运行时错误。编译错误通常由 rustc 直接指出错误行号和原因类型例如所有权冲突、类型不匹配、生命周期问题。修复依据是编译器给出的提示不需要靠猜。运行时错误则需要检查文件路径是否真实存在、是否有读取权限、命令行参数是否传递正确。Rust 编译器的错误信息质量在主流语言中属于比较高的它会尽量给出“如何修复”的提示。初学阶段遇到编译错误不要慌张优先阅读错误说明的后半部分那里经常直接告诉你应该怎么改。7. 常见问题与排查思路以下是 Rust 初学者最常遇到的一些问题整理成表格方便快速查阅。问题现象可能原因排查方式解决方案编译报错value moved here发生了所有权移动旧变量已被标记为无效之后仍在使用查看报错行检查是否在使用旧变量改用引用或显式调用clone()编译报错cannot borrow as mutable同一作用域内同时存在不可变借用和可变借用查看借用发生的位置和持续时间调整借用顺序确保可变引用结束后再创建不可变引用或缩短不可变引用的生命周期编译报错lifetime may not live long enough返回的引用与传入参数的生命周期关系不明确查看函数签名梳理引用来源为函数添加生命周期标注如a启动项目后cargo build下载依赖特别慢默认使用的是 crates.io 海外源执行cargo build -v查看卡在哪个下载步骤在~/.cargo/config.toml中配置国内镜像源Rust 编译速度慢全量编译所有依赖debug 模式仍需完整构建观察第二次构建是否更快检查是否每次都在清理 target 目录开发时多用cargo check代替cargo build不要频繁清理 target 目录程序读取中文文本统计结果不对字节数统计的是 UTF-8 编码后的字节长度不是字符数确认文本编码格式如果需要统计字符数改用text.chars().count()运行cargo run -- file时报参数错误忘记了--Cargo 会误把参数当成自己的参数查看命令是否包含--使用cargo run -- 文件名传入程序参数cargo test找不到测试函数测试函数没有标注#[test]属性或测试模块没被正确声明检查函数前是否有#[test]添加#[test]属性确保使用mod tests声明模块这里要特别展开说的是“可变借用冲突”问题。很多初学者写出类似这样的代码let mut s String::from(hello); let r1 s; let r2 mut s; println!({}, r1);这段代码在编译期就会报错因为不能同时持有不可变引用r1和可变引用r2。从数据竞争角度看编译器其实是在保护你的并发安全。解决方案很直接把不可变引用的使用范围缩短让r1在r2创建之前结束使用。let mut s String::from(hello); { let r1 s; println!({}, r1); } let r2 mut s;这种写法完全合法因为两个引用的生命周期没有重叠。8. Rust 开发最佳实践与工程建议8.1 从 CLI 工具和算法练习开始Rust 的学习曲线是真实存在的。相比脚本语言你写的第一段代码可能先要解决编译器报错而不是直接看到结果。这时候建议选择两类项目作为上手的突破口。一类是命令行工具主逻辑不复杂但能自然覆盖参数解析、文件读写、错误处理、依赖引用这些真实工程环节。另一类是算法题或数据结构实现只依赖标准库专注于核心语法和所有权语义。等你对所有权模型有了肌肉记忆再进入并发、网络编程、Web 服务等方向会顺畅得多。8.2 错误处理优先用 Result 而不是 panicRust 的错误处理有两种基本选择ResultT, E用于可恢复错误panic!用于不可恢复异常。实际工程中所有可预期的外部输入错误如文件不存在、网络超时、参数格式错误都应当返回Result由上层调用者决定如何处理。panic!应该保留给程序内部逻辑不可能出错但不小心出错的情况。一个常见错误是一开始为了省事在所有可能出错的地方都unwrap()这个函数的作用是“拿不到值就直接 panic”。小项目跑通没问题但一旦进入生产环境任何一次unwrap都可能成为线上崩溃点。推荐在代码评审阶段明确规则能在入口处集中处理的错误就集中处理不要向整个调用链散布 panic 风险。8.3 模块化组织而不是所有代码塞进 main.rsRust 项目的代码组织原则与其他语言一致按模块拆分职责。main.rs只负责入口流程业务逻辑放到独立模块中。模块内的函数应当职责单一并返回Result类型让调用方能够感知并处理错误。即使只是一个几百行的工具项目拆成lib.rs提供核心逻辑、main.rs负责参数解析和调用后续新增功能时的维护成本也会低很多。更重要的是单元测试可以直接测试库模块中的函数不必依赖命令行输入。8.4 保持依赖最小化Rust 的依赖解析系统会让第三方 crate 的传递依赖自动进入构建但是否真的需要依赖某些库需要认真判断。Rust 标准库本身功能丰富很多常见的路径处理、集合操作、时间处理可以直接用标准库实现。每引入一个依赖就增加了一份供应链风险也增加了编译时间。推荐在引入第三方 crate 前先问自己这真的是必需的吗标准库能否满足8.5 生产环境的三条底线对敏感数据的处理不要打印日志避免泄露。对外部输入进行长度和格式校验避免异常数据处理不当引发 panic。涉及资源释放和安全边界的位置先写测试再写功能测试就是你的安全网。Rust 的所有权模型在编译期解决了内存安全但不代表不需要处理业务安全。文件路径遍历、命令注入、密文泄漏这些应用层安全问题仍然要靠开发者自己把关。这也是系统编程语言永远绕不开的工程素质。9. 总结与后续学习方向这篇文章的核心意图是用一套完整的最小项目把 Rust 的“安全”和“快速”两个标签落到实处的理解路径所有权、借用、生命周期它们不是语法习题而是为了解决系统开发中最痛苦的内存安全问题而存在的设计。你把这三件事吃透再去看 Rust 的 trait、泛型、并发、宏都会觉得顺畅。从下一步的实践路径来说建议先趁热打铁把wordcount这个小工具扩展一下支持统计多个文件支持实时输出到终端支持从标准输入读取数据而不是只读文件。这些扩展会自然逼着你接触Result的传播、迭代器的使用、以及更复杂的函数签名恰好是 Rust 进阶路上的典型练习。挑一个自己工作里经常用到的命令行场景用 Rust 重新实现一遍比阅读再多文章都管用。Rust 并不是所有场景的银弹。Web 业务逻辑层、快速原型开发、脚本自动化这类场景Python、Go 或 Java 可能开发效率更高。Rust 真正的主场依然在系统软件、性能敏感组件、嵌入式、游戏引擎、网络基础设施这些需要精细控制资源的地方。判断一个技术该不该学最终看的是它在你的项目中是否创造了不可替代的价值。在系统开发这个领域选择 Rust 的本质是选择把更多可靠性问题交给编译器而不是留给深夜里排查线上事故的自己。如果你认同这个判断现在就可以创建第一个 Cargo 项目把今天看到的示例代码跑起来。值得收藏备用但更值得动手验证一遍。