第01章:Rust 语言概述
第01章:Rust 语言概述
1.1 Rust 的历史
诞生背景
Rust 最初由 Mozilla 员工 Graydon Hoare 于 2006 年作为个人项目开发,旨在创建一门安全且高效的系统编程语言。Mozilla 于 2009 年正式赞助该项目,目标是为 Firefox 浏览器引擎 Servo 提供底层支持。
发展时间线
| 年份 | 里程碑 |
|---|---|
| 2006 | Graydon Hoare 开始个人项目 |
| 2009 | Mozilla 正式赞助 |
| 2010 | 项目首次公开 |
| 2012 | 发布 0.1 版本,开始收集社区反馈 |
| 2015 | Rust 1.0 正式发布(5月15日),确立稳定性承诺 |
| 2018 | Rust 2018 Edition,引入异步基础语法 |
| 2021 | Rust 2021 Edition,改进闭包捕获和数组 IntoIterator |
| 2024 | Rust 2024 Edition,进一步简化异步编程 |
命名由来
Rust 以一种名为 锈菌(Rust Fungus) 的真菌命名,这种生物具有强大的生存能力和多态性,寓意语言本身的韧性和灵活性。
注意: Rust 语言的名称大小写始终为 Rust(首字母大写),而非 RUST 或 rust。但在代码和命令行中使用小写
rustc、cargo等。
1.2 设计哲学
Rust 的设计围绕三大核心原则:
安全性(Safety)
Rust 最显著的设计目标是在编译时保证内存安全,无需垃圾回收器(GC)。
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1 的所有权移动到 s2
// println!("{}", s1); // ❌ 编译错误:s1 已失效
println!("{}", s2); // ✅ 正常使用
}
并发性(Concurrency)
Rust 的类型系统和所有权规则天然防止数据竞争(data race):
use std::thread;
fn main() {
let mut data = vec![1, 2, 3];
// 所有权移动到新线程
let handle = thread::spawn(move || {
data.push(4);
println!("子线程: {:?}", data);
});
// println!("{:?}", data); // ❌ 编译错误:data 已移动
handle.join().unwrap();
}
零成本抽象(Zero-Cost Abstractions)
Rust 的抽象不会在运行时产生额外开销。你不用的东西不需付出代价,你用的东西手写也不会更好。
fn main() {
// 使用迭代器链(高级抽象)
let sum: i32 = (1..=100)
.filter(|x| x % 2 == 0)
.map(|x| x * x)
.sum();
println!("1到100中偶数的平方和: {}", sum);
// 编译器会将上述代码优化为与手写循环同样高效的机器码
}
与其他语言的哲学对比
| 特性 | C/C++ | Java/Go | Rust |
|---|---|---|---|
| 内存管理 | 手动 | GC | 所有权系统 |
| 空指针 | 存在 | 存在(null) | 不存在(Option) |
| 数据竞争 | 运行时暴露 | 运行时暴露 | 编译时阻止 |
| 抽象开销 | 可能有 | 有(GC 暂停) | 零成本 |
| 学习曲线 | 陡峭 | 平缓 | 陡峭但值得 |
1.3 内存安全模型
所有权系统(Ownership)
Rust 通过三条简单的规则管理内存:
- 每个值有且仅有一个所有者(owner)
- 同一时间只能有一个所有者
- 当所有者离开作用域,值被自动释放
fn main() {
{
let s = String::from("hello"); // s 是有效所有者
println!("{}", s);
} // s 离开作用域,String 被自动释放(调用 drop)
// println!("{}", s); // ❌ 编译错误:s 已不存在
}
借用规则(Borrowing Rules)
在任意时刻,满足以下其中一个条件:
- 有任意数量的不可变引用(
&T) - 有且仅有一个可变引用(
&mut T)
二者不能同时存在。
fn main() {
let mut s = String::from("hello");
let r1 = &s; // ✅ 不可变引用
let r2 = &s; // ✅ 不可变引用
println!("{} {}", r1, r2);
// r1 和 r2 在此之后不再使用(NLL)
let r3 = &mut s; // ✅ 可变引用(此时 r1、r2 已失效)
r3.push_str(", world");
println!("{}", r3);
}
与 C/C++ 内存安全问题的对比
| 常见漏洞 | C/C++ | Rust |
|---|---|---|
| 空指针解引用 | 运行时崩溃 | 编译时阻止(无 null) |
| 悬垂指针 | 未定义行为 | 编译时阻止(生命周期) |
| 缓冲区溢出 | 运行时漏洞 | 运行时 panic(边界检查) |
| 使用已释放内存 | 未定义行为 | 所有权系统阻止 |
| 数据竞争 | 未定义行为 | 编译时阻止(Send/Sync) |
| 双重释放 | 未定义行为 | 编译时阻止 |
注意: Rust 的内存安全保证仅适用于 Safe Rust。
unsafe块中仍然可能引入内存安全问题,但编译器会明确标记这些区域。
1.4 零成本抽象
编译器优化示例
Rust 编译器基于 LLVM 后端,能够将高级抽象优化为高效机器码:
// 情况 1: 使用迭代器
fn sum_squares_iterator(n: u64) -> u64 {
(1..=n).map(|x| x * x).sum()
}
// 情况 2: 使用手写循环
fn sum_squares_loop(n: u64) -> u64 {
let mut sum = 0u64;
for x in 1..=n {
sum += x * x;
}
sum
}
fn main() {
let n = 1000;
println!("迭代器: {}", sum_squares_iterator(n));
println!("手写循环: {}", sum_squares_loop(n));
// 两种方式生成的机器码几乎完全相同
}
泛型单态化(Monomorphization)
Rust 在编译时为每种具体类型生成专用代码:
// 泛型函数
fn max_value<T: PartialOrd>(a: T, b: T) -> T {
if a >= b { a } else { b }
}
fn main() {
// 编译器会生成两个版本:
// max_value_i32(a: i32, b: i32) -> i32
// max_value_f64(a: f64, b: f64) -> f64
println!("{}", max_value(10, 20));
println!("{}", max_value(3.14, 2.71));
}
注意: 单态化会增加编译后二进制文件的大小(代码膨胀),但运行时性能与手写具体类型版本相同。
1.5 适用场景
Rust 的强项领域
| 领域 | 说明 | 代表项目 |
|---|---|---|
| 系统编程 | 操作系统、驱动、嵌入式 | Redox OS, Tock |
| Web 后端 | 高性能 API 服务 | Actix-web, Axum, Rocket |
| WebAssembly | 浏览器高性能模块 | wasm-bindgen, Yew |
| CLI 工具 | 命令行工具开发 | ripgrep, bat, fd |
| 网络服务 | 代理、负载均衡 | Tokio, Hyper, Tower |
| 数据库 | 存储引擎 | TiKV, SurrealDB |
| 游戏开发 | 游戏引擎 | Bevy, Amethyst |
| 区块链 | 智能合约、节点 | Solana, Polkadot |
| 安全工具 | 静态分析、漏洞扫描 | Cargo-audit |
Rust 可能不是最佳选择的场景
| 场景 | 原因 | 替代方案 |
|---|---|---|
| 快速原型开发 | 编译慢、学习曲线陡 | Python, Ruby |
| 简单脚本 | 权重过高 | Python, Bash |
| GUI 桌面应用 | 生态仍在成熟中 | C# (WPF), Kotlin |
| 大型企业应用 | 开发效率较低 | Java, C# |
| 数据科学 | 库生态不足 | Python (NumPy/Pandas) |
1.6 第一个 Rust 程序
让我们快速感受 Rust 的风格:
use std::collections::HashMap;
/// 分析文本中每个单词的出现次数
fn word_count(text: &str) -> HashMap<&str, usize> {
let mut counts = HashMap::new();
for word in text.split_whitespace() {
// entry API:键不存在则插入默认值,返回可变引用
let count = counts.entry(word).or_insert(0);
*count += 1;
}
counts
}
fn main() {
let text = "hello world hello rust hello world";
let counts = word_count(text);
println!("单词统计结果:");
for (word, count) in &counts {
println!(" \"{}\" 出现 {} 次", word, count);
}
}
运行输出(顺序可能不同,HashMap 无序):
单词统计结果:
"rust" 出现 1 次
"hello" 出现 3 次
"world" 出现 2 次
1.7 Rust 与同类语言对比
与 C++ 对比
| 方面 | C++ | Rust |
|---|---|---|
| 内存安全 | 手动管理,容易出错 | 编译时保证 |
| 编译速度 | 较慢(模板) | 较慢(借用检查+单态化) |
| 运行时 | 极快 | 极快(可比肩) |
| ABI 稳定 | 无统一标准 | 无(需 FFI) |
| 并发安全 | 依赖程序员 | 编译器强制 |
与 Go 对比
| 方面 | Go | Rust |
|---|---|---|
| 学习曲线 | 平缓 | 陡峭 |
| 垃圾回收 | 有(GC 暂停) | 无(零开销) |
| 并发模型 | goroutine + channel | 线程 + async/await |
| 编译速度 | 极快 | 较慢 |
| 二进制大小 | 较大 | 较小 |
| 适用场景 | 微服务、云原生 | 系统、性能敏感 |
1.8 学习建议
克服"与借用检查器斗争"
初学者最常遇到的困难是与编译器的借用检查器(borrow checker)“斗争”。以下是建议:
- 接受编译器是朋友: 每个编译错误都在阻止一个潜在 bug
- 先理解生命周期: 理解值的存活范围是关键
- 使用 Clone 暂时绕过: 学习阶段可以用
.clone()消除借用问题,之后再优化 - 阅读错误信息: Rust 的错误信息质量极高,仔细阅读通常能找到解决方案
- 练习、练习、再练习: 通过 Rustlings 练习
推荐学习资源
| 资源 | 类型 | 适合人群 |
|---|---|---|
| The Rust Book | 官方教程 | 所有初学者 |
| Rust By Example | 示例教程 | 喜欢动手的人 |
| Rustlings | 练习题 | 巩固知识 |
| Rust 语言圣经 | 中文教程 | 中文读者 |
| Programming Rust (O’Reilly) | 书籍 | 深入学习 |
1.9 业务场景示例:为何选择 Rust
场景:构建高并发 API 网关
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::time::Instant;
/// 请求计数器(线程安全,无需锁)
struct RequestCounter {
total: AtomicU64,
errors: AtomicU64,
start_time: Instant,
}
impl RequestCounter {
fn new() -> Self {
Self {
total: AtomicU64::new(0),
errors: AtomicU64::new(0),
start_time: Instant::now(),
}
}
fn record_request(&self, is_error: bool) {
self.total.fetch_add(1, Ordering::Relaxed);
if is_error {
self.errors.fetch_add(1, Ordering::Relaxed);
}
}
fn report(&self) {
let elapsed = self.start_time().elapsed().as_secs_f64();
let total = self.total.load(Ordering::Relaxed);
let errors = self.errors.load(Ordering::Relaxed);
let qps = total as f64 / elapsed;
println!("总请求数: {}", total);
println!("错误数: {}", errors);
println!("QPS: {:.2}", qps);
}
}
fn main() {
let counter = Arc::new(RequestCounter::new());
// 模拟多线程并发请求
let handles: Vec<_> = (0..10)
.map(|_| {
let counter = Arc::clone(&counter);
std::thread::spawn(move || {
for i in 0..1000 {
counter.record_request(i % 50 == 0); // 每50个请求一个错误
}
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
counter.report();
}
选择 Rust 的理由
- 无 GC 暂停: 保证低延迟(P99 < 10ms)
- 内存安全: 减少线上因内存问题导致的崩溃
- 零成本抽象: 迭代器、泛型不牺牲性能
- 并发安全: 编译时防止数据竞争
- 小二进制: 适合容器化部署
1.10 本章小结
| 要点 | 说明 |
|---|---|
| Rust 历史 | 2006 年由 Graydon Hoare 创立,2015 年 1.0 发布 |
| 设计哲学 | 安全、并发、零成本抽象 |
| 内存安全 | 通过所有权系统和借用规则在编译时保证 |
| 零成本抽象 | 高级语法不牺牲运行时性能 |
| 适用场景 | 系统编程、Web 后端、CLI、嵌入式等 |
扩展阅读
- Rust 官方博客 — 版本发布和技术文章
- This Week in Rust — 每周 Rust 生态动态
- Rust 核心团队架构 — Rust 治理结构
- Oxide and Friends Podcast — 深度技术讨论
- RustConf 演讲视频 — 年度会议录像