Rust 编程入门教程
从零开始,系统学习 Rust 编程语言。本教程涵盖 Rust 核心概念、实战项目与最佳实践,帮助你掌握这门现代系统编程语言。无论你是从 C/C++ 转过来的系统程序员,还是从 Python/JavaScript 转过来的应用开发者,本教程都会为你提供清晰、循序渐进的学习路径。
目录
- Rust 语言简介与设计理念
- 环境搭建(rustup / cargo)
- 基本语法与数据类型
- 变量绑定与可变性
- 所有权系统
- 借用与生命周期
- 结构体与枚举
- 模式匹配
- 函数与方法
- 错误处理(Result / Option)
- 包与模块
- 泛型与 trait
- 迭代器与闭包
- 智能指针
- 并发编程
- 文件 I/O
- 测试
- 宏基础
- Web 开发入门
- 实战:命令行工具
- 实战:简易数据库
- 最佳实践
1. Rust 语言简介与设计理念
1.1 Rust 是什么
Rust 是一门由 Mozilla 研究院于 2010 年发起设计、目前由 Rust 基金会(Linux 基金会下属)维护的系统级编程语言。它的第一个稳定版本 1.0 于 2015 年 5 月正式发布。Rust 的核心设计目标是在不使用垃圾回收(Garbage Collection)的前提下,实现内存安全、线程安全和零成本抽象。换句话说,Rust 希望让开发者既能拥有像 C 和 C++ 那样的底层控制能力和极致性能,又能避免它们在内存管理方面容易引入的各种隐患。
Rust 连续多年在 Stack Overflow 年度开发者调查中被评为"最受喜爱的编程语言",这说明一旦开发者真正上手了 Rust,他们往往不愿意再回到其他语言。目前,Rust 被广泛应用于操作系统开发(如 Linux 内核模块)、浏览器引擎(如 Firefox 的 Servo)、区块链基础设施(如 Solana、Polkadot)、嵌入式系统、WebAssembly 前端编译目标、高性能命令行工具以及后端网络服务等多个领域。越来越多的科技巨头——包括微软、谷歌、亚马逊、Meta 和字节跳动——都在其核心基础设施中引入了 Rust。
Rust 的编译器非常严格,初学者经常会遇到"跟编译器斗争"的挫败感。但这种严格恰恰是 Rust 的价值所在——编译器拒绝通过的代码,往往就是存在潜在 bug 的代码。一旦你理解了编译器的规则,你会发现它就像一位经验丰富的导师,总是在你犯错时及时提醒你。
1.2 核心设计理念
内存安全(Memory Safety)
Rust 通过其独特的所有权系统(Ownership System)在编译期阶段就杜绝了绝大多数常见的内存安全问题。这些问题包括但不限于:空指针解引用(dereferencing null pointers)、悬垂指针(dangling pointers)、数据竞争(data races)、缓冲区溢出(buffer overflows)、使用已释放的内存(use-after-free)以及双重释放(double free)。在 C 和 C++ 中,这些错误往往在运行时才暴露,甚至可能潜伏数年之久才被触发,导致极其难以定位和修复的安全漏洞。据微软和谷歌的统计,其产品中约百分之七十的安全漏洞都属于内存安全问题。Rust 的做法是将这些检查前移到编译期,让编译器成为你的第一道防线。只要代码能通过编译,就能在很大程度上保证不存在上述内存安全问题。
零成本抽象(Zero-Cost Abstractions)
这是 Rust 从 C++ 借鉴并进一步发扬光大的理念。简单来说,就是你使用 Rust 提供的高级抽象特性——比如泛型(generics)、trait、迭代器(iterators)、闭包(closures)、模式匹配(pattern matching)等——在编译之后生成的机器码,与你手写底层循环和手动管理内存所产生的机器码在性能上完全一致。你不需要为使用高级语言特性而付出任何运行时性能代价。编译器会在编译阶段完成所有的单态化(monomorphization)和内联(inlining)优化,确保最终的可执行代码尽可能高效。这意味着你可以放心地使用高级特性来编写清晰、可维护的代码,而不用担心性能损失。
并发安全(Concurrency Safety)
Rust 的类型系统和借用检查器能够在编译阶段防止数据竞争。数据竞争是并发编程中最棘手、最难调试的 bug 类型之一,它发生在两个或多个线程同时访问同一块内存且至少有一个线程在进行写入操作时。Rust 通过 Send 和 Sync 这两个标记 trait(marker trait)来标识哪些类型可以安全地跨线程转移所有权,哪些类型可以安全地被多个线程共享引用。如果一段代码存在潜在的数据竞争风险,它根本无法通过编译。这种"如果编译通过,就不会有数据竞争"的保证,在整个编程语言领域都是极为罕见的。在其他语言中,你可能需要花费数小时甚至数天来排查一个并发 bug;而在 Rust 中,编译器会直接告诉你问题在哪里。
表达力(Expressiveness)
Rust 虽然是一门系统级语言,但它并不缺乏高级语言的表达力。它拥有强大的模式匹配(pattern matching)机制、带数据的枚举(enums with data,也称为代数数据类型或 ADT)、trait 系统(类似于接口但更强大)、闭包、迭代器链式调用等特性。这些特性使得 Rust 代码既安全又简洁,可以用较少的代码表达复杂的逻辑,同时保持极高的可读性和可维护性。Rust 吸收了函数式编程语言(如 Haskell、OCaml)的许多优秀设计理念,同时保持了命令式编程的直觉性。
1.3 Rust 与其他语言的对比
为了帮助你更好地理解 Rust 的定位,我们将它与其他几门主流语言做一个横向对比:
与 C/C++ 的对比
C 和 C++ 同样是系统级语言,提供了极致的性能和底层控制能力。但它们依赖程序员手动管理内存,这意味着程序员必须时刻牢记何时分配、何时释放,一旦疏忽就会导致内存泄漏、悬垂指针等问题。Rust 通过所有权系统在编译期自动管理内存,不需要垃圾回收,也不会引入额外的运行时开销,同时从根本上消除了这些手动管理容易犯的错误。此外,Rust 拥有现代化的包管理工具 Cargo,解决了 C/C++ 长期以来缺乏统一包管理标准的痛点。在语法层面,Rust 的错误信息比 C++ 的模板错误要友好得多,大大降低了调试的难度。
与 Java/Go 的对比
Java 和 Go 使用垃圾回收(GC)来自动管理内存,这大大降低了开发的心智负担,但 GC 会引入不可预测的暂停(GC pauses),在对延迟敏感的场景(如高频交易、游戏引擎、实时系统)中可能成为性能瓶颈。Rust 不使用 GC,内存的释放时机完全由编译器在编译期确定,运行时没有任何额外负担。在并发安全方面,Go 通过 goroutine 和 channel 提供了良好的并发模型,但仍然无法在编译期防止所有的数据竞争。Rust 的类型系统则可以在编译期做到这一点。在包管理方面,Go 的 Go Modules 和 Java 的 Maven/Gradle 都已经比较成熟,但 Rust 的 Cargo 在易用性和功能完整性方面仍然是业界标杆。
与 Python/Ruby 的对比
Python 和 Ruby 是动态脚本语言,开发效率极高,但运行性能较低,且类型安全性不足。Rust 在保持较高开发效率的同时,提供了接近 C 的运行性能。虽然 Rust 的编译时间比 Python 的解释执行慢,但生成的可执行程序在运行速度上通常快几十倍甚至上百倍。此外,Rust 的静态类型系统和编译期检查能在程序运行之前就发现大量错误,这在大型项目中尤为重要。近年来,越来越多的 Python 库开始使用 Rust 作为性能关键部分的实现语言(如 Pydantic v2、Ruff、Polars 等),这也从侧面证明了 Rust 在性能方面的优势。
1.4 Hello, World!
让我们从最经典的程序开始:
fn main() {
println!("你好,Rust!");
}
这段代码虽然简单,但包含了几个重要概念:
fn main()定义了程序的入口函数。每个 Rust 可执行程序都必须有一个main函数,它是程序开始执行的地方。println!是一个宏(macro),注意末尾的感叹号!。它用于将格式化文本输出到标准输出(stdout)。使用宏而非函数的原因是println!在编译期会根据格式字符串检查参数的数量和类型是否匹配,这是一种编译期的安全保证。宏在编译期展开,因此不会产生额外的运行时开销。- Rust 的代码块使用花括号
{}界定,语句末尾使用分号;。这些语法元素与 C、Java 等语言相似,降低了学习门槛。 - 注意中文字符串在 Rust 中完全可以直接使用,因为 Rust 的字符串默认采用 UTF-8 编码,天然支持所有 Unicode 字符。
2. 环境搭建(rustup / cargo)
2.1 安装 Rust
Rust 使用官方提供的 rustup 工具链管理器进行安装和版本管理。rustup 不仅负责安装 Rust 编译器本身,还管理多个版本的工具链、标准库文档以及各种辅助组件。这种统一的管理方式是 Rust 生态的一大优势——你不需要手动下载安装包、配置环境变量或担心版本冲突。
在 Linux 和 macOS 系统上,打开终端执行以下命令即可安装:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
这个命令会从 Rust 官方服务器下载安装脚本并执行。安装过程中会询问你选择安装类型,通常选择默认选项(输入 1 并回车)即可。安装完成后,需要让环境变量生效:
source $HOME/.cargo/env
或者直接关闭当前终端窗口并重新打开一个新的终端。之后,可以通过以下命令验证安装是否成功:
rustc --version
# 输出示例:rustc 1.77.0 (aedd173a2 2024-03-17)
cargo --version
# 输出示例:cargo 1.77.0 (3fe68eabf 2024-02-29)
rustup --version
# 输出示例:rustup 1.27.0 (bbb9276d2 2024-03-08)
在 Windows 系统上,你可以从 https://rustup.rs 下载安装程序,按照向导一步步完成安装。安装过程中会自动安装 Visual Studio 的 C++ 构建工具(如果尚未安装),因为 Rust 在 Windows 上默认使用 MSVC 链接器。
2.2 rustup 常用命令
rustup 是管理 Rust 工具链的核心命令。它不仅可以安装和更新 Rust,还可以管理多个版本的工具链——这对于需要在稳定版和夜间版之间切换的开发者来说非常有用。夜间版(nightly)包含了一些实验性的新特性,某些前沿的库可能需要夜间版才能编译。以下是一些最常用的子命令:
# 更新 Rust 到最新稳定版本
rustup update
# 查看当前已安装的所有工具链
rustup toolchain list
# 安装 nightly 版本工具链(包含实验性特性)
rustup toolchain install nightly
# 切换默认工具链为稳定版
rustup default stable
# 切换默认工具链为 nightly 版
rustup default nightly
# 添加交叉编译目标(例如编译 WebAssembly)
rustup target add wasm32-unknown-unknown
# 查看所有可用的编译目标
rustup target list
# 添加有用的组件
rustup component add clippy # 代码 lint 静态分析工具
rustup component add rustfmt # 代码自动格式化工具
rustup component add rust-src # 标准库源码(IDE 智能提示需要)
rustup component add rust-docs # 本地文档
# 查看当前使用的 Rust 版本详细信息
rustup show
2.3 Cargo —— Rust 的包管理与构建工具
Cargo 是 Rust 生态系统的核心基础设施。它不仅仅是一个构建工具,更是一个完整的项目管理平台,负责依赖管理、编译、测试、文档生成、代码发布等几乎所有开发环节。可以说,Cargo 是 Rust 相比 C/C++ 最大的体验优势之一。
创建新项目
# 创建一个新的二进制(可执行)项目
cargo new hello_rust
cd hello_rust
# 创建一个新的库项目
cargo new my_lib --lib
执行 cargo new hello_rust 后,Cargo 会自动生成以下项目结构:
hello_rust/
├── Cargo.toml # 项目配置文件,声明项目元信息和依赖
├── .gitignore # Git 忽略文件(Cargo 自动创建)
└── src/
└── main.rs # 程序入口源文件
Cargo.toml 配置文件详解
Cargo.toml 是 Rust 项目的核心配置文件,使用 TOML 格式编写。它声明了项目的名称、版本、作者信息以及所有外部依赖:
[package]
name = "hello_rust" # 包名称
version = "0.1.0" # 语义化版本号
edition = "2021" # Rust 版本年份(影响语言特性)
authors = ["Your Name <you@example.com>"]
description = "一个 Rust 入门示例项目"
license = "MIT"
[dependencies]
# 外部依赖声明
serde = { version = "1.0", features = ["derive"] } # 序列化框架
tokio = { version = "1", features = ["full"] } # 异步运行时
[dev-dependencies]
# 仅在测试和基准测试中使用的依赖
criterion = "0.5" # 基准测试框架
Cargo 常用命令
# 编译项目(debug 模式,编译速度快,不启用优化)
cargo build
# 编译项目(release 模式,启用所有优化,用于发布)
cargo build --release
# 编译并运行项目
cargo run
# 仅检查代码是否能通过编译(比 build 快很多,不生成可执行文件)
cargo check
# 运行所有测试
cargo test
# 运行指定名称的测试
cargo test test_add
# 生成项目文档并在浏览器中打开
cargo doc --open
# 使用 rustfmt 格式化所有代码
cargo fmt
# 使用 clippy 进行静态代码分析
cargo clippy
# 添加一个新的依赖
cargo add serde --features derive
cargo add tokio --features full
# 删除一个依赖
cargo remove tokio
# 构建用于发布的包
cargo package
# 清理编译产物
cargo clean
# 查看依赖树
cargo tree
2.4 编辑器与 IDE 配置
选择一个好的编辑器能大幅提升 Rust 开发体验。以下是主流选择:
VS Code(推荐)
VS Code 是目前 Rust 开发最流行的编辑器之一。你需要安装以下扩展:
- rust-analyzer:这是 Rust 官方推荐的语言服务器扩展,提供实时代码补全、类型提示、跳转到定义、自动导入、代码重构、内联类型标注等几乎所有 IDE 功能。它是目前体验最好的 Rust 语言服务。
- Even Better TOML:为
Cargo.toml等 TOML 配置文件提供语法高亮和格式化支持。 - Error Lens:将编译错误和警告直接显示在编辑器的对应代码行旁边,而不是只在底部状态栏显示,极大提升了错误发现和修复效率。
- CodeLLDB:Rust 调试器扩展,支持断点调试、变量查看、调用栈追踪等功能。
其他选择
- RustRover(JetBrains 出品):功能最全面的 Rust IDE,提供开箱即用的完整开发体验,包括调试器、测试运行器、数据库工具等。
- Neovim / Helix:适合喜欢终端编辑器的开发者,配合 LSP 插件同样能获得优秀的 Rust 开发体验。
3. 基本语法与数据类型
3.1 变量声明与类型推断
Rust 是一门静态类型语言,意味着每个变量在编译期就有确定的类型。但与其他静态类型语言不同的是,Rust 拥有强大的类型推断能力——在大多数情况下,你不需要手动标注类型,编译器会根据变量的值和使用上下文自动推断出正确的类型。只有在编译器无法推断(例如函数参数)或你想显式指定类型时,才需要手动标注。
fn main() {
// Rust 可以自动推断类型
let x = 5; // 编译器推断为 i32(默认整数类型)
let y = 3.14; // 编译器推断为 f64(默认浮点类型)
let name = "Rust"; // 编译器推断为 &str(字符串切片)
let is_active = true; // 编译器推断为 bool
// 显式类型标注(当需要或想要明确时)
let age: u8 = 25;
let temperature: f32 = 36.5;
let letter: char = 'A';
let count: u64 = 1_000_000; // 下划线用于提高大数字的可读性
println!("x={}, y={}, name={}, is_active={}", x, y, name, is_active);
println!("age={}, temp={}, letter={}, count={}", age, temperature, letter, count);
}
3.2 整数类型
Rust 提供了多种整数类型,分为有符号(signed,以 i 开头)和无符号(unsigned,以 u 开头)两大类,每类有 8 位、16 位、32 位、64 位和 128 位五种宽度。此外还有 isize 和 usize 两种平台相关类型,其宽度与运行平台的指针大小一致(32 位平台上是 32 位,64 位平台上是 64 位)。
fn main() {
// 有符号整数:可以表示负数
let a: i8 = 127; // 8 位,范围 -128 到 127
let b: i16 = 32_767; // 16 位,范围 -32768 到 32767
let c: i32 = -1000; // 32 位(默认整数类型),范围约 ±21 亿
let d: i64 = 9_223_372_036_854_775_807; // 64 位
let e: i128 = 170_141_183_460_469_231_731_687_303_715_884_105_727; // 128 位
// 无符号整数:只能表示非负数,正数范围翻倍
let f: u8 = 255; // 8 位,范围 0 到 255
let g: u16 = 65_535; // 16 位
let h: u32 = 4_294_967_295; // 32 位
let i: u64 = 18_446_744_073_709_551_615; // 64 位
// 平台相关类型(常用于集合索引)
let index: usize = 42; // 无符号,用于数组索引
let offset: isize = -10; // 有符号
// 不同进制的整数字面量
let decimal = 98_222; // 十进制
let hex = 0xff; // 十六进制
let octal = 0o77; // 八进制
let binary = 0b1111_0000; // 二进制
let byte = b'A'; // 字节字面量(仅限 u8 类型,值为 65)
println!("decimal={}, hex={}, octal={}, binary={}, byte={}",
decimal, hex, octal, binary, byte);
// 整数溢出:debug 模式会 panic,release 模式会自动回绕
// let overflow: u8 = 256; // debug 模式下编译期检查会报错
}
3.3 浮点数类型
Rust 提供两种浮点数类型,都遵循 IEEE 754 标准:
fn main() {
let x = 2.0; // f64,双精度浮点数(默认类型)
let y: f32 = 3.0; // f32,单精度浮点数
// 浮点数运算
let sum = 5.0 + 10.5; // 加法
let difference = 95.5 - 4.3; // 减法
let product = 4.0 * 30.0; // 乘法
let quotient = 56.7 / 32.2; // 除法
let remainder = 43.5 % 5.3; // 取余
println!("sum={}, diff={}, prod={}, quot={:.2}, rem={:.2}",
sum, difference, product, quotient, remainder);
// 注意:浮点数比较需要小心精度问题
let a = 0.1 + 0.2;
let b = 0.3;
println!("0.1 + 0.2 == 0.3 ? {}", a == b); // 可能是 false!
println!("差值:{:.20}", (a - b).abs());
// 推荐使用误差范围进行比较
let epsilon = 1e-10;
println!("近似相等?{}", (a - b).abs() < epsilon);
}
3.4 布尔类型与字符类型
fn main() {
// 布尔类型
let t = true;
let f: bool = false;
println!("true={}, false={}", t, f);
// 布尔运算
let and = true && false; // 逻辑与
let or = true || false; // 逻辑或
let not = !true; // 逻辑非
println!("and={}, or={}, not={}", and, or, not);
// 字符类型:Rust 的 char 占 4 字节,支持完整的 Unicode 标量值
let c = 'z';
let z = 'ℤ';
let heart = '❤';
let cat = '🐱';
let chinese = '中';
println!("c={}, z={}, heart={}, cat={}, chinese={}", c, z, heart, cat, chinese);
println!("char 的大小:{} 字节", std::mem::size_of::<char>());
// 字符可以转换为整数(Unicode 码点)
println!("'A' 的码点:{}", 'A' as u32); // 65
println!("'中' 的码点:{}", '中' as u32); // 20013
}
3.5 复合类型:元组
元组(Tuple)可以将多个不同类型的值组合成一个复合值。元组的长度是固定的,一旦声明就不能增减。
fn main() {
// 创建元组
let tup: (i32, f64, char) = (500, 6.4, 'A');
// 解构(destructuring):将元组拆分为独立变量
let (x, y, z) = tup;
println!("x={}, y={}, z={}", x, y, z);
// 通过点号加索引访问元组元素(索引从 0 开始)
println!("第一个元素:{}", tup.0);
println!("第二个元素:{}", tup.1);
println!("第三个元素:{}", tup.2);
// 元组可以作为函数的返回值,实现返回多个值
fn swap(a: i32, b: i32) -> (i32, i32) {
(b, a)
}
let (first, second) = swap(1, 2);
println!("交换后:{}, {}", first, second);
// 单元类型(unit type):空元组 (),表示"无值"
// 常用于不返回任何有意义值的函数
let unit: () = ();
println!("单元类型的大小:{} 字节", std::mem::size_of_val(&unit));
}
3.6 复合类型:数组
数组(Array)是固定长度的、所有元素类型相同的集合。数组存储在栈上,因此访问速度非常快。
fn main() {
// 声明数组
let arr = [1, 2, 3, 4, 5];
let first = arr[0]; // 访问第一个元素
let second = arr[1];
println!("first={}, second={}", first, second);
// 显式类型标注:[元素类型; 元素数量]
let months: [&str; 12] = [
"一月", "二月", "三月", "四月", "五月", "六月",
"七月", "八月", "九月", "十月", "十一月", "十二月"
];
println!("一年有 {} 个月,第一个月是 {}", months.len(), months[0]);
// 使用相同值初始化数组
let zeros = [0; 5]; // 等价于 [0, 0, 0, 0, 0]
println!("zeros = {:?}", zeros);
// 数组切片:获取数组的一部分引用
let slice = &arr[1..3]; // 包含索引 1 和 2 的元素,即 [2, 3]
println!("slice = {:?}", slice);
// 数组越界访问会导致运行时 panic(程序崩溃)
// Rust 在可能的情况下也会在编译期进行越界检查
// let out_of_bounds = arr[10]; // 运行时 panic: index out of bounds
}
3.7 字符串
Rust 中的字符串比大多数语言更复杂,因为它明确区分了字符串的所有权关系和内存布局:
fn main() {
// &str(字符串切片):不可变的、对某段 UTF-8 字节的引用
// 字符串字面量("hello")的类型就是 &str
let s1: &str = "你好,世界";
// String:可增长的、堆分配的、拥有所有权的字符串
let mut s2 = String::from("你好");
s2.push_str(",世界"); // 追加字符串切片
s2.push('!'); // 追加单个字符
println!("{}", s2);
// String 与 &str 之间的转换
let s3: &str = &s2; // String -> &str(自动解引用强制转换)
let s4: String = s1.to_string(); // &str -> String
let s5 = String::from(s1); // &str -> String(另一种方式)
println!("s3={}, s4={}, s5={}", s3, s4, s5);
// 字符串拼接的多种方式
let hello = String::from("Hello");
let world = String::from("World");
// 方式一:format! 宏(推荐,不消耗任何参数的所有权)
let greeting = format!("{} {}!", hello, world);
println!("{}", greeting);
// 方式二:push_str 和 push
let mut s = String::from("Hello");
s.push(' ');
s.push_str("World");
println!("{}", s);
// 方式三:+ 运算符(注意:会消耗左侧操作数的所有权)
let s6 = hello + " " + &world;
println!("{}", s6);
// println!("{}", hello); // 编译错误!hello 已被消耗
// 重要:String 不能通过索引直接访问字符
// 因为 UTF-8 编码中,一个字符可能占用 1 到 4 个字节
// let first = s6[0]; // 编译错误!
// 正确的遍历方式
for c in "你好世界".chars() {
print!("{} ", c);
}
println!();
// 获取字节数
println!("'你好' 的字节数:{}", "你好".len()); // 6(每个中文字符 3 字节)
println!("'hello' 的字节数:{}", "hello".len()); // 5
}
4. 变量绑定与可变性
4.1 不可变绑定
Rust 的变量默认是不可变的(immutable)。这是 Rust 安全设计理念的直接体现——默认不变,只有当你明确需要修改值时才使用 mut 关键字显式声明。这种设计减少了因意外修改变量导致的 bug,也让编译器能够进行更激进的优化。
fn main() {
let x = 5;
println!("x = {}", x);
// x = 6; // 编译错误!cannot assign twice to immutable variable `x`
}
当你尝试对不可变变量重新赋值时,编译器会给出明确的错误信息,告诉你这个变量是不可变的。这种编译期的安全检查是 Rust 的核心优势之一。
4.2 可变绑定
当你确实需要修改变量的值时,使用 mut 关键字显式声明:
fn main() {
let mut x = 5;
println!("x 的初始值:{}", x);
x = 10; // 合法,因为 x 被声明为 mut
println!("x 修改后的值:{}", x);
// 可变变量也可以进行复合赋值
x += 5;
x *= 2;
println!("经过运算后:{}", x); // 30
}
显式使用 mut 有一个额外的好处:当你在代码中看到 let mut 时,你就知道这个变量稍后会被修改,这为阅读代码的人提供了有价值的语义信息。如果一个变量声明了 mut 但从未被修改,编译器(通过 clippy)会发出警告,提示你可能不需要 mut。
4.3 变量遮蔽(Shadowing)
Rust 允许你用相同的名字重新声明一个变量,新变量会"遮蔽"(shadow)之前的同名变量。这与可变变量的重新赋值不同——遮蔽实际上创建了一个全新的变量,只是恰好与之前的变量同名而已。这意味着遮蔽甚至可以改变变量的类型。
fn main() {
let x = 5;
let x = x + 1; // 遮蔽前一个 x,现在 x = 6
let x = x * 2; // 再次遮蔽,现在 x = 12
println!("x = {}", x); // 输出 12
// 遮蔽可以改变类型(这是与 mut 的关键区别)
let spaces = " "; // 类型是 &str
let spaces = spaces.len(); // 类型变为 usize
println!("空格数量:{}", spaces);
// 如果使用 mut,则不能改变类型
// let mut spaces2 = " ";
// spaces2 = spaces2.len(); // 编译错误!类型不匹配
}
遮蔽在实际开发中非常有用。例如,当你从外部接收到一个字符串值,需要先做转换再使用时,可以用遮蔽来避免发明 value_str、value_int 这样的变体名称,直接用同一个名字 value 即可。
4.4 常量与静态变量
除了 let 绑定的变量,Rust 还有两种特殊的"变量"形式:
// 常量(const):编译期确定的值,不可变,不能使用 mut
// 常量必须标注类型,只能被赋值为编译期可求值的表达式
const MAX_POINTS: u32 = 100_000;
const PI: f64 = 3.14159265358979;
// 静态变量(static):有固定的内存地址,在整个程序运行期间都存在
// 静态变量的生命周期是 'static(整个程序)
static APP_NAME: &str = "MyRustApp";
// 可变静态变量:访问时必须使用 unsafe 块
// 因为多线程同时访问可变静态变量可能导致数据竞争
static mut COUNTER: u32 = 0;
fn main() {
println!("最大点数:{}", MAX_POINTS);
println!("圆周率:{}", PI);
println!("应用名称:{}", APP_NAME);
// 访问可变静态变量需要 unsafe
unsafe {
COUNTER += 1;
println!("计数器:{}", COUNTER);
}
}
常量与不可变变量的区别在于:常量必须标注类型,必须使用编译期可求值的表达式赋值,不能使用 mut,也不能被遮蔽。常量可以被声明在全局作用域中,而变量绑定则不能。
5. 所有权系统
所有权是 Rust 最独特、最核心的概念,也是它区别于几乎所有其他编程语言的根本特性。理解所有权系统是学好 Rust 的关键,也是很多初学者觉得最具挑战性的部分。但一旦真正理解了,你会发现它带来的安全保证是无与伦比的。
所有权系统的设计初衷非常简单:在没有垃圾回收器的情况下,如何在编译期就确定每块内存何时应该被释放?传统的做法有两种——要么由程序员手动管理(C/C++),要么由垃圾回收器自动管理(Java/Go/Python)。Rust 选择了第三条路:通过一套严格的编译规则,在编译期就确定每块内存的生命周期,运行时自动释放,既不需要程序员手动管理,也不需要垃圾回收器的运行时开销。
5.1 所有权规则
Rust 的所有权系统遵循三条简洁而强大的基本规则:
- Rust 中的每一个值都有一个所有者(owner)
- 同一时刻,一个值只能有一个所有者
- 当所有者离开作用域时,值将被自动释放(drop)
第三条规则意味着 Rust 不需要垃圾回收器——内存的释放时机在编译期就已经确定,运行时没有任何额外开销。这种确定性的资源释放还有一个额外的好处:你可以在 drop 方法中编写自定义的清理逻辑(如关闭文件句柄、释放网络连接),而这些清理逻辑的执行时机是完全可预测的。
fn main() {
{
let s = String::from("hello"); // s 从这里开始有效,它是字符串的所有者
println!("{}", s);
// 我们可以对 s 进行各种操作
} // s 离开作用域,Rust 自动调用 drop 函数释放字符串占用的堆内存
// println!("{}", s); // 编译错误!s 已经不存在了
}
5.2 移动语义(Move)
当把一个值赋给另一个变量时,所有权的转移方式取决于该类型是否实现了 Copy trait。对于堆上分配的类型(如 String、Vec 等),赋值操作会将所有权"移动"(move)到新变量,原变量随即失效。这种设计避免了两个变量同时试图释放同一块内存的问题(即"双重释放")。
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1 的所有权移动到 s2,s1 不再有效
// println!("{}", s1); // 编译错误!value borrowed here after move
println!("{}", s2); // 合法,s2 是新的所有者
// 对于栈上的简单类型(如整数、布尔、浮点数等实现了 Copy trait 的类型)
// 赋值操作是复制而非移动
let x = 5;
let y = x; // x 被复制,两个变量都有效
println!("x={}, y={}", x, y); // 完全合法
}
5.3 克隆(Clone)
如果你确实需要深拷贝一份堆上的数据,可以显式调用 clone() 方法:
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone(); // 深拷贝:堆上的数据被复制了一份
println!("s1={}, s2={}", s1, s2); // 两个变量都有效,各自拥有独立的数据
}
需要注意的是,clone() 可能是昂贵的操作,因为它涉及堆内存分配和数据复制。在性能敏感的代码中,应该谨慎使用 clone(),优先考虑借用(borrowing)的方式。
5.4 函数与所有权
将值传给函数时,会发生与变量赋值相同的语义:实现了 Copy trait 的类型会被复制,其他类型则会被移动。
fn takes_ownership(s: String) {
println!("函数获取到字符串的所有权:{}", s);
} // s 离开作用域,String 被释放
fn makes_copy(x: i32) {
println!("函数接收到 i32 的副本:{}", x);
} // x 被释放,但因为 i32 实现了 Copy,外部的变量不受影响
fn gives_ownership() -> String {
let s = String::from("你好");
s // 所有权转移给调用者
}
fn takes_and_gives_back(s: String) -> String {
println!("临时使用字符串:{}", s);
s // 所有权还回去
}
fn main() {
let s = String::from("hello");
takes_ownership(s);
// println!("{}", s); // 编译错误!s 的所有权已经移动到函数内部
let x = 5;
makes_copy(x);
println!("x 仍然有效:{}", x); // 合法,i32 是 Copy 类型
let s1 = gives_ownership();
println!("获得所有权:{}", s1);
let s2 = takes_and_gives_back(s1);
println!("拿回所有权:{}", s2);
}
5.5 Copy 与 Drop trait
Copy trait 和 Drop trait 是 Rust 所有权系统的两个重要基石:
- Copy trait:标记一个类型在赋值和函数传参时自动复制而非移动。只有栈上的简单类型才能实现 Copy。任何需要自定义清理逻辑的类型(即实现了 Drop trait 的类型)不能同时实现 Copy。
- Drop trait:定义当值离开作用域时自动执行的清理逻辑,类似于其他语言中的析构函数。
// 实现 Drop trait 来观察资源的释放时机
struct MyResource {
name: String,
}
impl Drop for MyResource {
fn drop(&mut self) {
println!("正在释放资源:{}", self.name);
}
}
fn main() {
let a = MyResource { name: String::from("资源A") };
let b = MyResource { name: String::from("资源B") };
println!("两个资源已创建");
// 离开作用域时,b 先被释放,a 后被释放(后进先出,LIFO 顺序)
// 可以在输出中观察到释放顺序
}
// 输出:
// 两个资源已创建
// 正在释放资源:资源B
// 正在释放资源:资源A
6. 借用与生命周期
6.1 引用与借用
所有权系统虽然安全,但如果每次使用数据都要转移所有权,代码会变得非常笨拙。引用(reference)允许你使用一个值但不获取它的所有权,这就是"借用"(borrowing)。引用就像一个指向数据的指针,但它受到 Rust 借用检查器的严格约束,确保始终有效。
引用分为两种:不可变引用(&T)和可变引用(&mut T)。
fn calculate_length(s: &String) -> usize {
// s 是对 String 的不可变引用,我们只是借用它,不拥有它
s.len()
} // s 离开作用域,但因为它不拥有值,所以值不会被释放
fn append_greeting(s: &mut String) {
// s 是对 String 的可变引用,我们可以修改它指向的值
s.push_str(",欢迎来到 Rust 的世界!");
}
fn main() {
let s = String::from("你好");
let len = calculate_length(&s); // 传递引用,不转移所有权
println!("'{}' 的长度是 {}", s, len); // s 仍然有效
let mut greeting = String::from("你好");
append_greeting(&mut greeting); // 传递可变引用
println!("{}", greeting);
}
6.2 借用规则
Rust 的借用规则是编译器强制执行的,它们确保了在任何时刻都不会出现数据竞争:
- 在任意给定时刻,你要么拥有一个可变引用,要么拥有任意数量的不可变引用——两者不能同时存在
- 引用必须始终有效(不能有悬垂引用)
第一条规则的关键在于:不可变引用之间互不干扰(因为谁都不会修改数据),但可变引用需要独占访问(因为修改数据时不能有其他人在读)。
fn main() {
let mut s = String::from("hello");
// 多个不可变引用——完全合法
let r1 = &s;
let r2 = &s;
println!("r1={}, r2={}", r1, r2);
// r1 和 r2 在此之后不再使用(NLL: Non-Lexical Lifetimes)
// 可变引用——合法,因为 r1 和 r2 已经不再使用了
let r3 = &mut s;
r3.push_str(" world");
println!("r3={}", r3);
// 以下代码会编译失败,因为不可变引用和可变引用同时存在
// let r4 = &s;
// let r5 = &mut s;
// println!("{}, {}", r4, r5); // 编译错误!
}
6.3 生命周期
生命周期(lifetime)是 Rust 编译器用来确保所有引用都始终有效的机制。大多数情况下,生命周期可以由编译器自动推断(称为"生命周期省略规则"),但在某些情况下——特别是当函数返回引用时——你需要显式标注生命周期,告诉编译器返回的引用与输入参数之间的存活关系。
// 'a 是生命周期参数,它告诉编译器:
// 返回的引用的生命周期与输入参数 x 和 y 中较短的那个一致
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let string1 = String::from("一个很长的字符串");
let result;
{
let string2 = String::from("短");
result = longest(string1.as_str(), string2.as_str());
println!("更长的是:{}", result);
}
// 注意:如果尝试在 string2 离开作用域之后使用 result,
// 编译器会报错,因为 result 可能引用了 string2 的数据
}
6.4 结构体中的生命周期
当结构体持有引用类型的字段时,必须标注生命周期参数,告诉编译器结构体实例的生命周期不能超过它所引用的数据:
// 'a 告诉编译器:Excerpt 实例的生命周期不能超过其 text 字段引用的数据
struct Excerpt<'a> {
text: &'a str,
}
impl<'a> Excerpt<'a> {
fn level(&self) -> i32 {
3
}
// 方法返回引用时,生命周期默认与 &self 相同(省略规则第三条)
fn announce_and_return(&self, announcement: &str) -> &str {
println!("公告:{}", announcement);
self.text
}
}
fn main() {
let novel = String::from("在很久很久以前。有一个小村庄...");
let first_sentence;
{
let i = novel.find('。').unwrap_or(novel.len());
first_sentence = &novel[..i];
}
let excerpt = Excerpt { text: first_sentence };
println!("摘录:{}", excerpt.text);
}
6.5 生命周期省略规则
编译器使用三条规则自动推断生命周期,大多数情况下你不需要手动标注:
- 每个引用参数都获得自己独立的生命周期参数
- 如果只有一个输入引用参数,其生命周期被赋给所有输出引用
- 如果有多个输入引用参数,但其中一个是
&self或&mut self(即这是一个方法),则self的生命周期被赋给所有输出引用
// 以下函数不需要显式生命周期标注,因为适用规则二
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &byte) in bytes.iter().enumerate() {
if byte == b' ' {
return &s[..i];
}
}
s
}
fn main() {
let sentence = String::from("hello world");
let word = first_word(&sentence);
println!("第一个单词:{}", word);
}
7. 结构体与枚举
7.1 结构体(Struct)
结构体是将多个相关字段组合在一起的自定义数据类型,类似于其他语言中的"类"或"记录":
// 经典结构体:每个字段都有名字
struct User {
username: String,
email: String,
active: bool,
sign_in_count: u64,
}
// 元组结构体:给元组一个名字,字段没有名字
struct Color(u8, u8, u8);
struct Point(f64, f64, f64);
// 单元结构体:没有任何字段,常用于标记类型
struct AlwaysEqual;
fn main() {
// 创建结构体实例
let user = User {
username: String::from("张三"),
email: String::from("zhangsan@example.com"),
active: true,
sign_in_count: 1,
};
println!("用户名:{}", user.username);
println!("邮箱:{}", user.email);
// 结构体更新语法:基于现有实例创建新实例
let user2 = User {
email: String::from("lisi@example.com"),
username: String::from("李四"),
..user // 其余字段从 user 复制
};
println!("新用户:{}", user2.username);
// 元组结构体的使用
let black = Color(0, 0, 0);
let origin = Point(0.0, 0.0, 0.0);
println!("黑色 RGB:({}, {}, {})", black.0, black.1, black.2);
println!("原点坐标:({}, {}, {})", origin.0, origin.1, origin.2);
}
7.2 结构体方法
使用 impl 块为结构体定义方法。方法的第一个参数是 self(代表当前实例),可以是 &self(不可变借用)、&mut self(可变借用)或 self(获取所有权)。
struct Rectangle {
width: f64,
height: f64,
}
impl Rectangle {
// 关联函数:不接收 self 参数,类似于其他语言中的"静态方法"
// 调用方式:Rectangle::new(30.0, 50.0)
fn new(width: f64, height: f64) -> Self {
Rectangle { width, height }
}
// 不可变方法:借用 self,不修改实例
fn area(&self) -> f64 {
self.width * self.height
}
// 可变方法:可变借用 self,可以修改实例
fn scale(&mut self, factor: f64) {
self.width *= factor;
self.height *= factor;
}
// 消费方法:获取 self 的所有权,调用后实例不再可用
fn into_dimensions(self) -> (f64, f64) {
(self.width, self.height)
}
// 周长计算
fn perimeter(&self) -> f64 {
2.0 * (self.width + self.height)
}
// 判断是否能包含另一个矩形
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
fn main() {
// 使用关联函数创建实例
let mut rect = Rectangle::new(30.0, 50.0);
println!("面积:{}", rect.area());
println!("周长:{}", rect.perimeter());
// 可变方法调用
rect.scale(2.0);
println!("缩放后面积:{}", rect.area());
let small = Rectangle::new(10.0, 20.0);
println!("rect 能包含 small 吗?{}", rect.can_hold(&small));
// 消费方法调用
let (w, h) = rect.into_dimensions();
println!("尺寸:{} x {}", w, h);
// println!("{}", rect.area()); // 编译错误!rect 已经被消费
}
7.3 枚举(Enum)
Rust 的枚举远比其他语言中的枚举强大——每个变体可以携带不同类型和数量的数据。这种"带数据的枚举"在函数式编程中称为"代数数据类型"(ADT),它为建模复杂数据提供了极大的灵活性。
// 定义枚举
enum Message {
Quit, // 无数据
Move { x: i32, y: i32 }, // 命名字段(类似结构体)
Write(String), // 包含一个 String
ChangeColor(i32, i32, i32), // 包含三个 i32
}
// 为枚举实现方法
impl Message {
fn call(&self) {
match self {
Message::Quit => println!("收到退出指令"),
Message::Move { x, y } => println!("移动到坐标 ({}, {})", x, y),
Message::Write(text) => println!("收到消息:{}", text),
Message::ChangeColor(r, g, b) => println!("颜色变为 ({}, {}, {})", r, g, b),
}
}
}
fn main() {
let messages = vec![
Message::Quit,
Message::Move { x: 10, y: 20 },
Message::Write(String::from("你好 Rust")),
Message::ChangeColor(255, 128, 0),
];
for msg in &messages {
msg.call();
}
}
7.4 Option 枚举
Option<T> 是 Rust 标准库中最重要、使用最广泛的枚举之一。它用于表示一个值可能存在也可能不存在的情况,从根本上替代了其他语言中的 null 或 nil 概念。在 Rust 中,不存在"空指针"——要么你有一个 Some(value),要么你有一个 None,并且你必须显式处理这两种情况。
// 标准库中的定义(无需手动定义,直接使用即可)
// enum Option<T> {
// Some(T), // 包含一个值
// None, // 没有值
// }
fn find_user(id: u32) -> Option<String> {
if id == 1 {
Some(String::from("张三"))
} else {
None
}
}
fn main() {
// 使用 match 处理 Option
let user = find_user(1);
match user {
Some(name) => println!("找到用户:{}", name),
None => println!("用户不存在"),
}
// 使用 if let 简化只关心一种情况的匹配
if let Some(name) = find_user(2) {
println!("找到:{}", name);
} else {
println!("未找到用户 2");
}
// Option 的常用方法
let x: Option<i32> = Some(5);
let y: Option<i32> = None;
println!("unwrap: {}", x.unwrap()); // 5(None 时会 panic)
println!("unwrap_or: {}", y.unwrap_or(0)); // 0(提供默认值)
println!("is_some: {}", x.is_some()); // true
println!("is_none: {}", y.is_none()); // true
println!("map: {:?}", x.map(|v| v * 2)); // Some(10)
println!("and_then: {:?}", x.and_then(|v| Some(v + 1))); // Some(6)
println!("unwrap_or_else: {}", y.unwrap_or_else(|| 42)); // 42
}
8. 模式匹配
8.1 match 表达式
match 是 Rust 中最强大的控制流结构之一。它类似于其他语言中的 switch,但功能远比 switch 强大。match 的一个关键特性是它必须穷尽所有可能的情况——如果漏掉了一种情况,编译器会报错。这确保你不会遗漏任何边界条件。
enum Coin {
Penny,
Nickel,
Dime,
Quarter(String), // 携带附加数据
}
fn value_in_cents(coin: &Coin) -> u32 {
match coin {
Coin::Penny => {
println!("幸运便士!");
1
}
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("来自 {} 的 25 美分硬币", state);
25
}
}
}
fn main() {
let coins = vec![
Coin::Penny,
Coin::Quarter(String::from("阿拉斯加")),
Coin::Dime,
Coin::Quarter(String::from("德克萨斯")),
];
let mut total = 0;
for coin in &coins {
total += value_in_cents(coin);
}
println!("总计:{} 美分", total);
}
8.2 模式的多种用法
模式匹配在 Rust 中的使用场景远不止 match,它可以在 if let、while let、for 循环、函数参数、let 绑定等多个地方使用:
fn main() {
// 匹配字面量
let x = 1;
match x {
1 => println!("一"),
2 => println!("二"),
3 => println!("三"),
_ => println!("其他数字"), // _ 通配符匹配所有未列出的情况
}
// 匹配多个值和范围
match x {
1 | 2 => println!("一或二"), // 多个模式用 | 分隔
3..=5 => println!("三到五之间"), // 范围匹配
_ => println!("其他"),
}
// 解构结构体
struct Point { x: i32, y: i32 }
let p = Point { x: 0, y: 7 };
match p {
Point { x, y: 0 } => println!("在 x 轴上,x={}", x),
Point { x: 0, y } => println!("在 y 轴上,y={}", y),
Point { x, y } => println!("不在坐标轴上:({}, {})", x, y),
}
// 匹配守卫(match guard):在模式匹配基础上添加额外条件
let num = Some(4);
match num {
Some(x) if x < 0 => println!("负数:{}", x),
Some(x) if x == 0 => println!("零"),
Some(x) => println!("正数:{}", x),
None => println!("无值"),
}
// @ 绑定:在匹配模式的同时捕获值
match 5 {
n @ 1..=5 => println!("在范围内,值为 {}", n),
_ => println!("不在范围内"),
}
}
8.3 if let 和 while let
当你只关心一种匹配情况时,match 显得过于冗长。if let 和 while let 提供了更简洁的语法:
fn main() {
// if let:只关心一种情况
let config_max = Some(3u8);
if let Some(max) = config_max {
println!("最大值配置为:{}", max);
}
// while let:循环直到模式不匹配
let mut stack = vec![1, 2, 3, 4, 5];
while let Some(top) = stack.pop() {
println!("弹出:{}", top);
}
// 循环结束后 stack 为空
// 在 for 循环中解构
let points = vec![(1, 2), (3, 4), (5, 6)];
for (x, y) in &points {
println!("点:({}, {})", x, y);
}
}
9. 函数与方法
9.1 函数定义与返回值
// 基本函数
fn greet(name: &str) {
println!("你好,{}!", name);
}
// 带返回值的函数:最后一个表达式(不带分号)自动作为返回值
fn add(a: i32, b: i32) -> i32 {
a + b // 没有分号——这是一个表达式,其值被返回
}
// 使用 return 关键字提前返回
fn check_age(age: u32) -> &'static str {
if age >= 18 {
return "已成年"; // 提前返回
}
"未成年" // 最后一个表达式,省略 return
}
// 返回多个值(使用元组)
fn divide(dividend: i32, divisor: i32) -> (i32, i32) {
(dividend / divisor, dividend % divisor)
}
// 函数作为参数(高阶函数)
fn apply_operation(f: fn(i32, i32) -> i32, a: i32, b: i32) -> i32 {
f(a, b)
}
fn multiply(a: i32, b: i32) -> i32 {
a * b
}
fn main() {
greet("世界");
println!("3 + 4 = {}", add(3, 4));
let (quotient, remainder) = divide(17, 5);
println!("17 / 5 = {} 余 {}", quotient, remainder);
println!("年龄检查:{}", check_age(20));
println!("apply(multiply, 3, 4) = {}", apply_operation(multiply, 3, 4));
}
10. 错误处理(Result / Option)
Rust 的错误处理哲学独树一帜:它没有异常(exception)机制。可恢复的错误使用 Result<T, E> 类型表示,不可恢复的错误使用 panic! 宏终止程序。这种设计让错误处理成为类型系统的一部分——如果一个函数可能失败,它的返回类型就会体现这一点,调用者必须显式处理错误,不能"忘记"。
10.1 不可恢复的错误:panic!
fn main() {
// panic! 会立即终止当前线程,展开调用栈并清理资源
// 主要用于程序遇到了不应该发生的错误(bug)
// panic!("发生了一个不可恢复的错误!");
// 数组越界访问会导致 panic
let v = vec![1, 2, 3];
// v[99]; // 运行时 panic: index out of bounds
// 设置环境变量 RUST_BACKTRACE=1 可以在 panic 时打印完整的调用栈
// 这对调试非常有帮助
println!("正常运行中...");
}
10.2 可恢复的错误:Result
use std::fs::File;
use std::io::{self, Read};
// Result<T, E> 枚举:Ok(T) 表示成功,Err(E) 表示失败
fn read_file(path: &str) -> Result<String, io::Error> {
let mut file = File::open(path)?; // ? 运算符:出错时提前返回错误
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
}
fn main() {
// 基本的 Result 匹配处理
let file = File::open("hello.txt");
match file {
Ok(f) => println!("文件打开成功:{:?}", f),
Err(e) => println!("文件打开失败:{}", e),
}
// expect 方法:成功时返回值,失败时 panic 并显示自定义消息
// 适用于你确定不会失败的场景,或者在原型开发阶段
// let f = File::open("hello.txt").expect("无法打开 hello.txt");
// ? 运算符是错误传播的最佳方式
match read_file("hello.txt") {
Ok(content) => println!("文件内容:{}", content),
Err(e) => println!("读取失败:{}", e),
}
// unwrap_or_else:失败时执行闭包提供默认值
let content = File::open("hello.txt")
.and_then(|mut file| {
let mut s = String::new();
file.read_to_string(&mut s)?;
Ok(s)
})
.unwrap_or_else(|_| String::from("默认内容"));
println!("内容:{}", content);
}
10.3 自定义错误类型
在实际项目中,通常需要定义自己的错误类型来统一处理不同来源的错误:
use std::fmt;
use std::num::ParseIntError;
#[derive(Debug)]
enum AppError {
ParseError(ParseIntError),
ValidationError(String),
NotFound,
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AppError::ParseError(e) => write!(f, "解析错误:{}", e),
AppError::ValidationError(msg) => write!(f, "验证错误:{}", msg),
AppError::NotFound => write!(f, "资源未找到"),
}
}
}
// 实现 From trait,让 ? 运算符自动转换错误类型
impl From<ParseIntError> for AppError {
fn from(e: ParseIntError) -> Self {
AppError::ParseError(e)
}
}
fn parse_age(input: &str) -> Result<u32, AppError> {
let age: u32 = input.parse()?; // 自动将 ParseIntError 转换为 AppError
if age > 150 {
Err(AppError::ValidationError(String::from("年龄不能超过 150")))
} else {
Ok(age)
}
}
fn main() {
let test_cases = vec!["25", "abc", "200"];
for input in test_cases {
match parse_age(input) {
Ok(age) => println!("'{}' -> 年龄:{}", input, age),
Err(e) => println!("'{}' -> 错误:{}", input, e),
}
}
}
11. 包与模块
11.1 模块系统
Rust 的模块系统用于组织代码、控制作用域和可见性。pub 关键字用于公开项,没有 pub 的项默认是私有的。
mod restaurant {
// 公开模块:外部可以访问
pub mod hosting {
pub fn add_to_waitlist() {
println!("已加入等待列表");
seat_at_table(); // 可以调用同级的私有函数
}
fn seat_at_table() {
println!("安排座位");
}
}
// 私有模块:外部无法直接访问
mod serving {
pub fn take_order() {
println!("接受订单");
}
}
pub mod back_of_house {
pub fn prepare_food() {
println!("准备食物");
}
// 公开结构体,但字段默认私有
pub struct Breakfast {
pub toast: String, // 公开字段
seasonal_fruit: String, // 私有字段
}
impl Breakfast {
pub fn summer(toast: &str) -> Breakfast {
Breakfast {
toast: String::from(toast),
seasonal_fruit: String::from("桃子"),
}
}
}
// 公开枚举的所有变体都自动公开
pub enum Appetizer {
Soup,
Salad,
}
}
}
fn main() {
// 绝对路径调用
crate::restaurant::hosting::add_to_waitlist();
// 相对路径调用
restaurant::hosting::add_to_waitlist();
let mut meal = restaurant::back_of_house::Breakfast::summer("全麦");
meal.toast = String::from("黑麦");
println!("我要 {} 吐司", meal.toast);
// meal.seasonal_fruit = String::from("蓝莓"); // 编译错误!私有字段
let _soup = restaurant::back_of_house::Appetizer::Soup;
}
11.2 use 关键字与路径简化
mod math {
pub mod arithmetic {
pub fn add(a: i32, b: i32) -> i32 { a + b }
pub fn sub(a: i32, b: i32) -> i32 { a - b }
}
pub mod geometry {
pub fn circle_area(r: f64) -> f64 {
std::f64::consts::PI * r * r
}
}
}
use math::arithmetic::{add, sub};
use math::geometry::circle_area;
// 使用 as 起别名(解决命名冲突)
use std::fmt::Result as FmtResult;
use std::io::Result as IoResult;
// 嵌套引入语法
use std::{cmp::Ordering, io};
// 等价于:
// use std::cmp::Ordering;
// use std::io;
// 引入模块下所有公开项(谨慎使用,可能导致命名冲突)
use std::collections::*;
fn main() {
println!("3 + 4 = {}", add(3, 4));
println!("10 - 5 = {}", sub(10, 5));
println!("圆面积:{}", circle_area(5.0));
let mut map = HashMap::new();
map.insert("key", "value");
println!("{:?}", map);
}
12. 泛型与 trait
12.1 泛型
泛型允许你编写适用于多种类型的通用代码,避免重复:
// 泛型函数
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in &list[1..] {
if item > largest {
largest = item;
}
}
largest
}
// 泛型结构体
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn new(x: T, y: T) -> Self {
Point { x, y }
}
fn x(&self) -> &T {
&self.x
}
}
// 仅为 f64 类型实现特定方法
impl Point<f64> {
fn distance_from_origin(&self) -> f64 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
fn main() {
let numbers = vec![34, 50, 25, 100, 65];
println!("最大的数字:{}", largest(&numbers));
let chars = vec!['y', 'm', 'a', 'q'];
println!("最大的字符:{}", largest(&chars));
let integer_point = Point::new(5, 10);
let float_point = Point::new(1.0, 4.0);
println!("整数点:({}, {})", integer_point.x, integer_point.y);
println!("浮点点到原点的距离:{}", float_point.distance_from_origin());
}
12.2 Trait
Trait 定义了共享行为,类似于其他语言中的接口,但功能更加强大。你可以为任何类型实现 trait,包括你没有定义的类型(在一定限制下)。
// 定义 trait
trait Summary {
fn summarize(&self) -> String;
// 默认实现
fn preview(&self) -> String {
format!("{}...", &self.summarize()[..20.min(self.summarize().len())])
}
}
struct Article {
title: String,
author: String,
content: String,
}
struct Tweet {
username: String,
content: String,
}
impl Summary for Article {
fn summarize(&self) -> String {
format!("《{}》—— 作者:{}", self.title, self.author)
}
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("@{}: {}", self.username, self.content)
}
}
// trait 作为参数
fn notify(item: &impl Summary) {
println!("通知:{}", item.summarize());
}
// trait bound 语法(更正式的写法)
fn notify2<T: Summary>(item: &T) {
println!("通知:{}", item.summarize());
}
fn main() {
let article = Article {
title: String::from("Rust 入门指南"),
author: String::from("张三"),
content: String::from("Rust 是一门优秀的语言..."),
};
let tweet = Tweet {
username: String::from("rustacean"),
content: String::from("刚学完所有权,太棒了!"),
};
println!("{}", article.summarize());
println!("{}", tweet.summarize());
notify(&article);
notify(&tweet);
}
12.3 常用标准库 Trait
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
struct Color {
r: u8,
g: u8,
b: u8,
}
impl fmt::Display for Color {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "#{:02X}{:02X}{:02X}", self.r, self.g, self.b)
}
}
impl From<(u8, u8, u8)> for Color {
fn from(tuple: (u8, u8, u8)) -> Self {
Color { r: tuple.0, g: tuple.1, b: tuple.2 }
}
}
impl Default for Color {
fn default() -> Self {
Color { r: 0, g: 0, b: 0 }
}
}
fn main() {
let red = Color { r: 255, g: 0, b: 0 };
println!("红色:{}", red);
println!("调试格式:{:?}", red);
let cloned = red.clone();
println!("克隆相等?{}", red == cloned);
let blue: Color = (0, 0, 255).into();
println!("蓝色:{}", blue);
let default_color = Color::default();
println!("默认颜色:{}", default_color);
}
13. 迭代器与闭包
13.1 闭包
闭包是可以捕获其所在环境中变量的匿名函数。它们在 Rust 中被广泛使用,特别是在迭代器适配器和异步编程中。
fn main() {
// 基本闭包语法
let add = |a: i32, b: i32| -> i32 { a + b };
println!("3 + 4 = {}", add(3, 4));
// 类型通常可以省略,编译器会推断
let multiply = |a, b| a * b;
println!("3 * 4 = {}", multiply(3, 4));
// 捕获环境变量
let name = String::from("Rust");
let greet = || println!("你好,{}!", name);
greet();
// move 闭包:强制获取环境变量的所有权
let data = vec![1, 2, 3];
let owns_data = move || {
println!("拥有数据:{:?}", data);
};
owns_data();
// println!("{:?}", data); // 编译错误!data 已被 move
// 闭包作为函数参数
fn apply_to_3(f: impl Fn(i32) -> i32) -> i32 {
f(3)
}
let double = |x| x * 2;
let square = |x| x * x;
println!("double(3) = {}", apply_to_3(double));
println!("square(3) = {}", apply_to_3(square));
}
13.2 迭代器
迭代器是 Rust 中处理序列数据的核心抽象。Rust 的迭代器是惰性的(lazy)——调用 .iter() 不会立即产生元素,只有当你消费迭代器时才会真正计算。
fn main() {
let v = vec![1, 2, 3, 4, 5];
// iter():产生不可变引用
for val in v.iter() {
print!("{} ", val);
}
println!();
// into_iter():获取所有权
let v2 = vec![1, 2, 3];
let sum: i32 = v2.into_iter().sum();
println!("求和:{}", sum);
// iter_mut():产生可变引用
let mut v3 = vec![1, 2, 3];
for val in v3.iter_mut() {
*val *= 2;
}
println!("翻倍后:{:?}", v3);
// 迭代器适配器(惰性,不会立即执行)
let doubled: Vec<i32> = vec![1, 2, 3, 4, 5]
.iter()
.map(|x| x * 2)
.collect();
println!("翻倍:{:?}", doubled);
// 链式调用
let result: Vec<i32> = (1..=10)
.filter(|x| x % 2 == 0) // 保留偶数
.map(|x| x * x) // 求平方
.collect();
println!("偶数的平方:{:?}", result);
// enumerate:带索引迭代
for (i, val) in vec!["a", "b", "c"].iter().enumerate() {
println!("索引 {}:{}", i, val);
}
// zip:并行迭代两个迭代器
let names = vec!["Alice", "Bob", "Charlie"];
let scores = vec![95, 87, 92];
for (name, score) in names.iter().zip(scores.iter()) {
println!("{}:{} 分", name, score);
}
// fold:归约操作
let sum = (1..=100).fold(0, |acc, x| acc + x);
println!("1 到 100 的和:{}", sum);
}
13.3 自定义迭代器
struct Counter {
count: u32,
max: u32,
}
impl Counter {
fn new(max: u32) -> Counter {
Counter { count: 0, max }
}
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.count < self.max {
self.count += 1;
Some(self.count)
} else {
None
}
}
}
fn main() {
let counter = Counter::new(5);
let result: Vec<u32> = counter.collect();
println!("计数器:{:?}", result); // [1, 2, 3, 4, 5]
// 自定义迭代器可以使用所有标准迭代器适配器
let sum: u32 = Counter::new(5)
.zip(Counter::new(5).skip(1))
.map(|(a, b)| a * b)
.filter(|x| x % 3 == 0)
.sum();
println!("计算结果:{}", sum);
}
14. 智能指针
14.1 Box<T>:堆上分配
Box<T> 是最简单的智能指针,它在堆上分配数据,栈上只保存一个指向堆数据的指针。最常见的用途是实现递归类型和动态分发。
// 递归类型必须使用 Box,因为编译器需要在编译期知道类型的大小
enum List {
Cons(i32, Box<List>),
Nil,
}
use List::{Cons, Nil};
fn main() {
// 创建链表:1 -> 2 -> 3 -> Nil
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
// 遍历链表
let mut current = &list;
loop {
match current {
Cons(val, next) => {
print!("{} -> ", val);
current = next;
}
Nil => {
println!("Nil");
break;
}
}
}
// Box 也可以用于将大数据放在堆上,避免栈溢出
let big_array = Box::new([0u8; 1_000_000]);
println!("大数组长度:{}", big_array.len());
}
14.2 Rc<T>:引用计数共享
Rc<T>(Reference Counting)允许同一份数据有多个所有者。每当克隆一个 Rc 时,引用计数加一;当一个 Rc 被丢弃时,引用计数减一;当引用计数降为零时,数据被释放。
use std::rc::Rc;
fn main() {
let a = Rc::new(String::from("共享数据"));
println!("a 的引用计数:{}", Rc::strong_count(&a));
let b = Rc::clone(&a); // 引用计数 +1
println!("克隆后引用计数:{}", Rc::strong_count(&a));
let c = Rc::clone(&a); // 引用计数 +1
println!("再次克隆后引用计数:{}", Rc::strong_count(&a));
drop(c); // 引用计数 -1
println!("drop c 后引用计数:{}", Rc::strong_count(&a));
println!("数据:{}", a);
}
14.3 Arc<T>:线程安全的引用计数
Arc(Atomic Reference Counting)是 Rc 的线程安全版本,使用原子操作来管理引用计数,可以在多线程环境中安全使用。
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for i in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
println!("线程 {} 将计数增加到 {}", i, *num);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("最终计数:{}", *counter.lock().unwrap());
}
15. 并发编程
15.1 线程
Rust 通过操作系统线程实现并发。std::thread::spawn 创建新线程,join 等待线程完成。
use std::thread;
use std::time::Duration;
fn main() {
// 创建线程
let handle = thread::spawn(|| {
for i in 1..=5 {
println!("子线程:{}", i);
thread::sleep(Duration::from_millis(100));
}
});
// 主线程继续执行
for i in 1..=3 {
println!("主线程:{}", i);
thread::sleep(Duration::from_millis(150));
}
// 等待子线程完成
handle.join().unwrap();
// move 闭包:将数据移入线程
let data = vec![1, 2, 3, 4, 5];
let handle = thread::spawn(move || {
let sum: i32 = data.iter().sum();
sum // 线程可以返回值
});
let result = handle.join().unwrap();
println!("子线程计算结果:{}", result);
}
15.2 消息传递
Rust 提供了基于通道(channel)的消息传递机制,遵循"不要通过共享内存来通信,而是通过通信来共享内存"的理念。
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
// 创建通道:tx 是发送端,rx 是接收端
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let messages = vec!["你好", "来自", "子线程"];
for msg in messages {
tx.send(String::from(msg)).unwrap();
thread::sleep(Duration::from_millis(200));
}
});
// 接收消息(rx 本身是迭代器)
for received in rx {
println!("收到:{}", received);
}
}
15.3 共享状态:Mutex
当你确实需要多个线程访问同一块数据时,可以使用 Mutex(互斥锁)来确保同一时刻只有一个线程能访问数据。
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
// Arc<Mutex<T>> 是多线程共享可变数据的标准模式
let shared_data = Arc::new(Mutex::new(vec![1, 2, 3]));
let mut handles = vec![];
for i in 0..5 {
let data = Arc::clone(&shared_data);
let handle = thread::spawn(move || {
let mut vec = data.lock().unwrap();
vec.push(i);
println!("线程 {} 添加了元素 {}", i, i);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("最终数据:{:?}", shared_data.lock().unwrap());
}
16. 文件 I/O
16.1 读写文件
use std::fs;
use std::fs::File;
use std::io::{self, BufRead, BufReader, Write};
fn main() -> io::Result<()> {
// 写入文件
let mut file = File::create("example.txt")?;
writeln!(file, "第一行:Hello, Rust!")?;
writeln!(file, "第二行:文件操作示例")?;
// 一次性读取整个文件
let content = fs::read_to_string("example.txt")?;
println!("文件内容:\n{}", content);
// 使用 BufReader 逐行读取(适合大文件)
let file = File::open("example.txt")?;
let reader = BufReader::new(file);
for (i, line) in reader.lines().enumerate() {
println!("第 {} 行:{}", i + 1, line?);
}
// 追加写入
let mut file = fs::OpenOptions::new()
.append(true)
.open("example.txt")?;
writeln!(file, "第三行:追加的内容")?;
// 获取文件元数据
let metadata = fs::metadata("example.txt")?;
println!("文件大小:{} 字节", metadata.len());
// 清理
fs::remove_file("example.txt")?;
Ok(())
}
16.2 目录与路径操作
use std::fs;
use std::path::Path;
fn main() -> std::io::Result<()> {
// 创建目录
fs::create_dir_all("test_dir/sub/sub2")?;
// 遍历目录
for entry in fs::read_dir(".")? {
let entry = entry?;
let path = entry.path();
let file_type = if path.is_dir() { "目录" } else { "文件" };
println!(" [{}] {}", file_type, path.display());
}
// 路径操作
let path = Path::new("/home/user/report.txt");
println!("文件名:{:?}", path.file_name());
println!("扩展名:{:?}", path.extension());
println!("父目录:{:?}", path.parent());
// 构建路径
let base = Path::new("/home/user");
let full = base.join("documents").join("report.pdf");
println!("完整路径:{}", full.display());
// 清理
fs::remove_dir_all("test_dir")?;
Ok(())
}
17. 测试
17.1 单元测试
Rust 内置了测试框架,使用 #[test] 属性标记测试函数,cargo test 运行所有测试。
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
pub fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(String::from("除数不能为零"))
} else {
Ok(a / b)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
assert_eq!(add(-1, 1), 0);
assert_eq!(add(0, 0), 0);
}
#[test]
fn test_divide_success() {
assert!(divide(10.0, 2.0).is_ok());
assert_eq!(divide(10.0, 2.0).unwrap(), 5.0);
}
#[test]
fn test_divide_by_zero() {
assert!(divide(10.0, 0.0).is_err());
}
#[test]
#[should_panic(expected = "除数不能为零")]
fn test_unwrap_panics() {
divide(1.0, 0.0).unwrap();
}
#[test]
fn test_with_result() -> Result<(), String> {
let result = divide(10.0, 2.0)?;
if result != 5.0 {
return Err(String::from("结果不正确"));
}
Ok(())
}
}
fn main() {}
17.2 测试命令
cargo test # 运行所有测试
cargo test test_add # 运行名称包含 "test_add" 的测试
cargo test -- --show-output # 显示 println! 输出
cargo test -- --test-threads=1 # 单线程运行测试
cargo test --lib # 仅运行库的单元测试
18. 宏基础
18.1 声明式宏(macro_rules!)
// 定义一个简单的宏
macro_rules! say_hello {
() => {
println!("你好!");
};
($name:expr) => {
println!("你好,{}!", $name);
};
}
// 自定义 vec! 宏
macro_rules! my_vec {
() => { Vec::new() };
($($element:expr),+ $(,)?) => {
{
let mut v = Vec::new();
$(v.push($element);)+
v
}
};
}
// hashmap 字面量宏
macro_rules! hashmap {
($($key:expr => $value:expr),* $(,)?) => {
{
let mut map = std::collections::HashMap::new();
$(map.insert($key, $value);)*
map
}
};
}
fn main() {
say_hello!();
say_hello!("世界");
let v = my_vec![1, 2, 3, 4, 5];
println!("my_vec: {:?}", v);
let scores = hashmap! {
"Alice" => 95,
"Bob" => 87,
};
println!("成绩:{:?}", scores);
}
18.2 常用标准库宏
fn main() {
// println! / format! / eprintln!
println!("Hello, {}!", "Rust");
println!("{:>10}", "右对齐");
println!("{:0>5}", 42); // 00042
println!("{:.2}", 3.14159); // 3.14
println!("{:#?}", vec![1, 2, 3]); // 美化调试输出
// vec! / dbg!
let v = vec![1, 2, 3];
let x = dbg!(42 * 2); // 打印表达式和值
// todo! / unimplemented!
// fn not_yet() -> i32 { todo!("还没实现") }
// cfg! 条件编译
if cfg!(target_os = "linux") {
println!("运行在 Linux");
}
}
19. Web 开发入门
Rust 拥有多个成熟的 Web 框架。以下以 Actix-web 和 Axum 为例展示基本用法。
19.1 Actix-web 示例
# Cargo.toml
[dependencies]
actix-web = "4"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
// use actix_web::{web, App, HttpServer, HttpResponse};
// use serde::{Deserialize, Serialize};
//
// #[derive(Serialize, Deserialize)]
// struct Message { content: String, author: String }
//
// async fn index() -> HttpResponse {
// HttpResponse::Ok().json(serde_json::json!({ "message": "欢迎!" }))
// }
//
// async fn greet(path: web::Path<String>) -> HttpResponse {
// HttpResponse::Ok().json(serde_json::json!({ "greeting": format!("你好,{}!", path) }))
// }
//
// #[actix_web::main]
// async fn main() -> std::io::Result<()> {
// HttpServer::new(|| {
// App::new()
// .route("/", web::get().to(index))
// .route("/greet/{name}", web::get().to(greet))
// })
// .bind("127.0.0.1:8080")?
// .run()
// .await
// }
fn main() {
println!("Web 开发需要添加 actix-web 依赖");
}
20. 实战:命令行工具
20.1 使用 clap 构建命令行应用
# Cargo.toml
[dependencies]
clap = { version = "4", features = ["derive"] }
// use clap::Parser;
//
// /// 文件统计工具
// #[derive(Parser, Debug)]
// #[command(author, version, about)]
// struct Args {
// /// 文件路径
// #[arg(short, long)]
// file: String,
//
// /// 统计模式
// #[arg(short, long, value_enum, default_value_t = Mode::Lines)]
// mode: Mode,
// }
//
// #[derive(clap::ValueEnum, Clone, Debug)]
// enum Mode { Lines, Words, Chars }
//
// fn main() {
// let args = Args::parse();
// let content = std::fs::read_to_string(&args.file).expect("无法读取文件");
// let result = match args.mode {
// Mode::Lines => content.lines().count(),
// Mode::Words => content.split_whitespace().count(),
// Mode::Chars => content.chars().count(),
// };
// println!("{}", result);
// }
fn main() {
println!("clap 是 Rust 最流行的命令行解析库");
}
20.2 简单命令行计算器
use std::io::{self, Write};
fn calculate(expr: &str) -> Result<f64, String> {
let parts: Vec<&str> = expr.split_whitespace().collect();
if parts.len() != 3 {
return Err(String::from("格式:<数字> <运算符> <数字>"));
}
let a: f64 = parts[0].parse().map_err(|_| "无效的数字")?;
let op = parts[1];
let b: f64 = parts[2].parse().map_err(|_| "无效的数字")?;
match op {
"+" => Ok(a + b),
"-" => Ok(a - b),
"*" | "x" => Ok(a * b),
"/" => {
if b == 0.0 {
Err(String::from("不能除以零"))
} else {
Ok(a / b)
}
}
"%" => Ok(a % b),
_ => Err(format!("未知运算符:{}", op)),
}
}
fn main() {
println!("简易计算器(输入 quit 退出)");
println!("格式:<数字> <运算符> <数字>,例如:3 + 4");
loop {
print!("> ");
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
let input = input.trim();
if input == "quit" || input == "exit" {
println!("再见!");
break;
}
match calculate(input) {
Ok(result) => println!("= {}", result),
Err(e) => println!("错误:{}", e),
}
}
}
21. 实战:简易数据库
21.1 内存键值存储
这个实战项目演示了如何用 Rust 构建一个简单的交互式键值数据库,包含命令解析、数据存储和用户交互功能。
use std::collections::HashMap;
use std::io::{self, Write, BufRead};
struct Database {
data: HashMap<String, String>,
}
impl Database {
fn new() -> Self {
Database { data: HashMap::new() }
}
fn set(&mut self, key: &str, value: &str) {
self.data.insert(key.to_string(), value.to_string());
println!("✓ 已设置 {} = {}", key, value);
}
fn get(&self, key: &str) {
match self.data.get(key) {
Some(value) => println!("{} = {}", key, value),
None => println!("✗ 键 '{}' 不存在", key),
}
}
fn delete(&mut self, key: &str) {
match self.data.remove(key) {
Some(_) => println!("✓ 已删除 '{}'", key),
None => println!("✗ 键 '{}' 不存在", key),
}
}
fn list(&self) {
if self.data.is_empty() {
println!("数据库为空");
} else {
println!("数据库内容({} 条记录):", self.data.len());
let mut entries: Vec<_> = self.data.iter().collect();
entries.sort_by_key(|(k, _)| k.to_lowercase());
for (key, value) in entries {
println!(" {} = {}", key, value);
}
}
}
}
fn main() {
let mut db = Database::new();
let stdin = io::stdin();
println!("╔═══════════════════════════════════╗");
println!("║ Rust 简易键值数据库 v0.1 ║");
println!("║ 命令:set/get/del/list/quit ║");
println!("╚═══════════════════════════════════╝");
loop {
print!("🦀 > ");
io::stdout().flush().unwrap();
let mut input = String::new();
stdin.lock().read_line(&mut input).unwrap();
let parts: Vec<&str> = input.trim().splitn(3, ' ').collect();
match parts[0].to_lowercase().as_str() {
"set" if parts.len() >= 3 => db.set(parts[1], parts[2]),
"get" if parts.len() >= 2 => db.get(parts[1]),
"del" if parts.len() >= 2 => db.delete(parts[1]),
"list" => db.list(),
"quit" | "exit" => {
println!("再见!");
break;
}
"" => {}
_ => println!("未知命令,输入 set/get/del/list/quit"),
}
}
}
22. 最佳实践
22.1 代码风格与工具
// 命名规范
const MAX_RETRIES: u32 = 3; // 常量:SCREAMING_SNAKE_CASE
static APP_VERSION: &str = "1.0.0"; // 静态变量:SCREAMING_SNAKE_CASE
struct UserProfile { } // 类型:PascalCase
trait Serializable { } // trait:PascalCase
fn calculate_total(items: &[f64]) -> f64 { // 函数/变量:snake_case
items.iter().sum()
}
// 开发工具链
// cargo fmt - 自动格式化代码
// cargo clippy - 静态分析,发现常见错误和改进点
// cargo test - 运行测试
// cargo doc - 生成文档
// cargo audit - 检查依赖的安全漏洞
// cargo tree - 查看依赖树
fn main() {}
22.2 错误处理最佳实践
use std::fs;
// 不要滥用 unwrap(),优先使用 ? 运算符传播错误
fn read_config() -> Result<String, std::io::Error> {
let content = fs::read_to_string("config.toml")?; // 好:用 ? 传播
Ok(content)
}
// unwrap 可以接受的场景
fn unwrap_examples() {
// 1. 测试代码中
let value = "42".parse::<i32>().unwrap();
// 2. 你能证明不会失败时
let s = String::from("hello");
let _first = s.chars().next().unwrap(); // 非空字符串一定有第一个字符
// 3. 原型开发阶段(之后替换为正确的错误处理)
let _config = fs::read_to_string("config.toml")
.expect("配置文件必须存在");
}
fn main() {}
22.3 性能优化
fn main() {
// 1. 优先使用迭代器而非手动循环(编译器会优化)
let sum: i32 = (0..1000).sum();
// 2. 预分配容器容量
let mut v = Vec::with_capacity(1000);
for i in 0..1000 {
v.push(i);
}
// 3. 优先使用 &str 而非 String(当不需要修改时)
fn greet(name: &str) { println!("你好,{}", name); }
greet("Rust");
// 4. 避免不必要的克隆,优先使用引用
let s = String::from("hello");
let len = s.len(); // 好:只借用
// 5. 使用 Cow 实现写时复制
use std::borrow::Cow;
fn process(input: &str) -> Cow<str> {
if input.contains(' ') {
Cow::Owned(input.replace(' ', "_"))
} else {
Cow::Borrowed(input)
}
}
println!("{}", process("hello world"));
}
22.4 学习路径建议
初级阶段(掌握基础):
├── 理解所有权和借用(最重要!反复练习直到内化)
├── 熟悉基本语法、数据类型和控制流
├── 掌握错误处理(Result/Option 和 ? 运算符)
├── 学会使用 Cargo 管理项目和依赖
└── 练习标准库常用模块(collections、io、fmt 等)
中级阶段(深入理解):
├── 深入理解 trait 和泛型
├── 掌握生命周期标注
├── 学会使用闭包和迭代器链
├── 理解智能指针(Box、Rc、Arc、Mutex)
├── 学习并发编程(线程、通道、共享状态)
└── 阅读优秀开源项目代码
高级阶段(精通运用):
├── unsafe Rust 和原始指针
├── 过程宏开发
├── 异步编程(tokio、async/await)
├── 性能分析与优化
├── FFI(与 C/C++ 互操作)
└── 参与 Rust 社区和开源项目
推荐阅读资源:
├── 《The Rust Programming Language》(官方书籍,免费在线阅读)
├── Rust by Example(带示例的交互式学习)
├── Rustlings(小练习,适合动手实践)
├── 《Rust 程序设计语言》中文版
└── crates.io 上的优秀开源项目
总结
恭喜你完成了 Rust 编程入门教程!让我们回顾一下最核心的要点:
- 所有权系统是 Rust 的灵魂——每个值有且只有一个所有者,离开作用域自动释放,无需垃圾回收
- 借用与生命周期让你安全地共享数据,编译器在编译期追踪所有引用的有效性
- trait 系统提供了强大的抽象能力,类似于接口但更灵活,支持默认实现
- 模式匹配让你优雅地处理复杂数据结构,编译器确保你穷尽了所有情况
- Result/Option 让错误处理成为类型系统的一部分,不会遗漏任何错误
- 零成本抽象意味着你可以在不牺牲性能的前提下编写高级、清晰的代码
- Cargo 是一流的包管理和构建工具,让依赖管理和项目组织变得轻松愉快
Rust 的学习曲线确实较陡,尤其是所有权和生命周期的概念需要时间来内化。但一旦你"过了编译器那关",你会发现它帮你捕获了大量在其他语言中难以发现的 bug。坚持练习,多读优秀项目的源码,多写代码,你会越来越欣赏这门语言的设计哲学。
"The compiler is your friend, not your enemy." 编译器是你的朋友,不是你的敌人。
祝你编程愉快!🦀