Rust 语言教程
这是一份专门为零基础小白准备的 Rust 语言教程。我会用最通俗的大白话来讲解,尽量把那些看着吓人的概念,变成你身边容易理解的东西。
第 0 章:为什么要学 Rust?
先别管技术细节,咱们得知道这玩意儿能干嘛,以及为什么最近这么火。
- 极致的性能:运行速度和 C、C++ 一个级别,但不用你手动管内存,编译器帮你盯着。
- 内存安全:没有空指针、悬垂指针、数据竞争这些让老程序员都头疼的 bug。编译的时候,编译器就像个严厉的老师,所有错误都给你指出来,不让你带到运行的时候。
- 现代化工具链:自带的包管理工具
cargo非常好用,创建项目、下载依赖、编译、测试一条龙。 - 应用广泛:从操作系统内核、浏览器引擎,到后端服务、命令行工具、区块链、嵌入式,都能干。
一句话:想写底层又怕 C++ 的坑,想写高层又嫌性能浪费,来学 Rust 就对了。
第 1 章:搭好环境,写第一个程序
1.1 安装 Rust
打开官网 rustup.rs,按提示下载安装 rustup。这是 Rust 的安装管理工具。
安装完成后,打开命令行(终端),输入:
rustc --version
如果看到类似 rustc 1.80.0 (xxxxxxxxx 2024-xx-xx) 的字样,说明安装成功。
1.2 安装 VS Code 和插件
推荐用 VS Code 来写代码,装一个叫 rust-analyzer 的插件,就有代码补全和错误提示了,跟写其他语言一样舒服。
1.3 第一个程序:Hello, World!
新建一个文件夹,比如 my_rust_project,在里面创建 main.rs 文件,用编辑器打开,写入:
fn main() {
println!("Hello, Rust!");
}
然后在该目录下打开终端,运行:
rustc main.rs # 编译
./main # 在Windows上是 main.exe
看到 Hello, Rust! 就成功啦!
但我们日常会用更强大的工具——cargo。
1.4 用 Cargo 创建项目
cargo new hello_cargo
cd hello_cargo
cargo new 会自动帮你生成一个项目骨架:
Cargo.toml:项目的配置文件,记录名字、版本、依赖库。src/main.rs:主代码文件,里面已经有个 hello world 了。
直接运行:
cargo run
cargo run 会自动编译并运行。太方便了!
第 2 章:基础语法——“规定,就这么写”
2.1 变量和不可变性
Rust 里默认变量是不可变的,像刻在石头上一样。一旦给了值,就不能改。这是 Rust 帮你防 bug 的一大法宝。
let x = 5;
// x = 6; // 这么写会报错!
如果你确实需要改,加个 mut(mutable 的缩写):
let mut y = 5;
y = 6; // 没问题
2.2 常量和静态变量
常量用 const,必须标明类型,而且值必须能在编译时算出来。名字习惯全大写。
const MAX_POINTS: u32 = 100_000;
2.3 数据类型
Rust 是静态类型语言,所有变量的类型在编译时就确定了。编译器通常能自动推断,但当有多种可能时,需要你手动注明。
整数:i32(有符号 32 位)、u64(无符号 64 位)等。默认是 i32。浮点数:f32、f64(默认)。布尔:bool,值为 true 或 false。字符:char,用单引号,支持 Unicode,能放 emoji:let heart = '❤';元组:可以把不同类型的数据打包在一起,用括号。
let tup: (i32, f64, char) = (500, 6.4, '嗨');
let (x, y, z) = tup; // 解构
let first = tup.0; // 用索引访问
数组:固定长度,同一类型,用方括号。
let arr = [1, 2, 3, 4, 5];
let first = arr[0];
// 数组越界访问在运行时会直接 panic,不给黑客机会。
2.4 函数
函数用 fn 声明,命名习惯用蛇形命名法 snake_case。
fn add(a: i32, b: i32) -> i32 {
return a + b; // 传统写法
}
fn sub(a: i32, b: i32) -> i32 {
a - b // 表达式直接返回,不加分号
}
注意:Rust 里,表达式(不加分号)会返回值,语句(加分号)不返回值。函数体最后一个表达式就是返回值。
2.5 控制流
if 表达式:
let num = 6;
if num % 2 == 0 {
println!("偶数");
} else {
println!("奇数");
}
// if 本身就是表达式,可以赋值
let result = if num > 5 { "大了" } else { "小了" };
循环:
// loop 无限循环,直到遇见 break
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // break 可以带值出去
}
};
// while 条件循环
while counter != 0 {
counter -= 1;
}
// for 遍历集合
let arr = [10, 20, 30];
for element in arr.iter() {
println!("值: {}", element);
}
// 经典的计数循环(左闭右开)
for number in 0..5 {
println!("{}", number); // 0,1,2,3,4
}
第 3 章:Rust 的绝学——所有权
这是 Rust 最独特、也最核心的概念。理解它,你就入门了。
所有权规则(三条铁律):
- Rust 中每一个值都有一个唯一的所有者。
- 同一时间,值只能有一个所有者。
- 当所有者离开作用域,值就被自动丢弃(释放内存)。
类比:一本书,同一时间只能属于一个人。你把书送给朋友(移动),你自己就没有了。朋友看完把书扔了(离开作用域),书就消失了。
3.1 移动(Move)
let s1 = String::from("hello");
let s2 = s1; // 这里 s1 的所有权转移给了 s2
// println!("{}", s1); // 错误!s1 已经失效了。
对于简单类型(如整数,编译时就知道大小,存在栈上),会自动复制,不会发生所有权转移。
let x = 5;
let y = x; // x 还能用,因为整数是 Copy 类型
3.2 克隆(Clone)
如果你真想深拷贝一份数据,用 clone。
let s1 = String::from("hello");
let s2 = s1.clone(); // s1 和 s2 各有一份独立的数据
3.3 引用和借用
如果把所有权带走,原变量就废了,太麻烦。有时我们只想“借来看看”,用完还回去。这就叫借用。
不可变引用:用 & 表示,可以同时存在多个。
fn print_len(s: &String) { // 借用一个引用,不拿所有权
println!("长度: {}", s.len());
}
let s = String::from("hello");
print_len(&s); // 借给函数
println!("{}", s); // s 还能用
可变引用:用 &mut 表示,但同一时间只能有一个可变引用,不能同时有不可变引用。避免数据竞争。
fn append_world(s: &mut String) {
s.push_str(" world");
}
let mut s = String::from("hello");
append_world(&mut s);
println!("{}", s); // hello world
引用必须始终有效:编译器会检查引用的生命周期,绝不让它指向已经被释放的内存。
第 4 章:构建自己的数据类型
4.1 结构体(struct)
把若干相关的数据打包成一个整体。
struct User {
username: String,
email: String,
active: bool,
}
let user1 = User {
email: String::from("a@b.com"),
username: String::from("张三"),
active: true,
};
// 访问
println!("{}", user1.email);
// 其他实例基于旧实例更新
let user2 = User {
email: String::from("c@d.com"),
..user1 // 其余字段沿用 user1 的,注意:username 被移动后 user1 就不能再用了
};
结构体也可以有方法,用 impl 块定义:
impl User {
fn say_hello(&self) {
println!("你好,我是 {}", self.username);
}
}
user2.say_hello();
4.2 枚举(enum)
枚举是一种可以表示“从几个固定选项里选一个”的类型。Rust 的枚举超级强大,每个选项还能带数据。
enum Message {
Quit, // 无数据
Move { x: i32, y: i32 }, // 匿名结构体
Write(String), // 元组结构体
ChangeColor(i32, i32, i32),
}
4.3 模式匹配(match)
match 就像一把瑞士军刀,能优雅地解构枚举,而且必须穷举所有可能,否则编译不过。
let msg = Message::Write(String::from("嘿嘿"));
match msg {
Message::Quit => println!("退出"),
Message::Move { x, y } => println!("移到 ({}, {})", x, y),
Message::Write(text) => println!("写: {}", text),
Message::ChangeColor(r, g, b) => println!("颜色 ({},{},{})", r, g, b),
}
还有 if let 可以只处理一种模式,其他忽略:
if let Message::Write(text) = msg {
println!("只处理 Write: {}", text);
}
4.4 神奇的 Option 和 Result
Rust 没有 null,那怎么表示“可能没有值”?用 Option<T> 枚举。
enum Option
Some(T),
None,
}
let some_number = Some(5);
let absent_number: Option
你要想用里面的值,必须用 match 或 unwrap 等方法显式处理 None 的情况,强制你考虑空值,避免空指针崩溃。
错误处理用 Result<T, E>:
enum Result<T, E> {
Ok(T),
Err(E),
}
比如打开文件可能失败,就会返回 Result。你需要处理成功和失败两种情况,不能装看不见。
use std::fs::File;
let f = File::open("hello.txt");
match f {
Ok(file) => println!("打开成功"),
Err(error) => println!("打开失败: {}", error),
}
? 操作符可以简化错误传递:如果结果是 Ok,取出里面的值;如果是 Err,直接向上层返回错误。
第 5 章:常用集合
5.1 动态数组 Vec<T>
let mut v = vec![1, 2, 3];
v.push(4);
let third = &v[2]; // 索引访问,越界会 panic
match v.get(2) { // get 返回 Option<&T>,更安全
Some(val) => println!("{}", val),
None => println!("没这个元素"),
}
5.2 字符串 String
Rust 的字符串有些复杂,因为底层是 UTF-8 编码的字节序列。不能用索引直接取单个字符。
let mut s = String::from("你好");
s.push_str("世界");
// 遍历字符
for c in s.chars() {
println!("{}", c);
}
5.3 哈希映射 HashMap<K, V>
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert("blue", 10);
scores.insert("red", 20);
let team = "blue";
let score = scores.get(team); // 返回 Option<&i32>
第 6 章:让代码更通用的武器——泛型和 Trait
6.1 泛型
写一份代码,适配多种类型。
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}
这里的 <T: PartialOrd + Copy> 是说:T 必须是能比较大小、能复制的类型。这叫 Trait 约束。
6.2 Trait(类似其他语言的接口)
trait 定义了一组行为,不同的类型可以实现这个 trait。
trait Summary {
fn summarize(&self) -> String;
}
struct NewsArticle {
headline: String,
content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}...", &self.headline)
}
}
// 以后可以这样用
fn notify(item: &impl Summary) {
println!("快讯:{}", item.summarize());
}
trait 也可以有默认实现,这样某些方法可以不写。
第 7 章:生命周期——给引用上“保险”
还记得吗?引用必须始终有效。但有时编译器搞不清两个引用到底谁活得更久,就需要我们标注生命周期,告诉编译器它们之间的关系。
大部分时候,编译器自己能推导。但函数参数中有多个引用时,就要手动标注。
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
这里 'a 就是一个生命周期的名字,它表示:返回的引用的生命周期,和两个输入引用的生命周期中较小的那个一样长。这样编译器就能检查,保证返回的引用不会指向已经销毁的数据。
初次接触会觉得符号多,别怕,先知道有这东西就行,写的多了自然就习惯了。
第 8 章:闭包和迭代器——简洁又好用
8.1 闭包
闭包就是可以捕获周围环境变量的匿名函数。写起来像小箭头。
let add_one = |x| x + 1;
println!("{}", add_one(5));
也能捕获外部变量:
let num = 10;
let plus_num = |x| x + num;
println!("{}", plus_num(5)); // 15
8.2 迭代器
Rust 的迭代器是惰性的,你调用了消耗性的方法(如 collect、sum)它才真正干活。
let v = vec![1, 2, 3];
let v2: Vec
println!("{:?}", v2); // [2, 4, 6]
链式调用读起来像英语,还零开销抽象,性能跟手写循环一样。
第 9 章:智能指针——更灵活的内存管理
有时候所有权规则比较严格,智能指针能给我们一点额外的“特权”。
-
Box<T>:把数据放在堆上,栈上只放一个指针。用于递归类型或大块数据转移所有权。let b = Box::new(5);
println!("{}", b); -
Rc<T>:引用计数指针,让一个值可以有多个“所有者”,用于单线程场景,不可变引用共享。use std::rc::Rc;
let a = Rc::new(String::from("共享文本"));
let b = a.clone(); // 只是克隆指针,不深拷贝数据 -
RefCell<T>:提供“内部可变性”,允许在只有不可变引用的情况下修改内部值。但有运行时检查,如果违反借用规则会 panic。
Rc 和 RefCell 经常搭配使用,实现多所有者且可变的共享数据(在单线程里)。
第 10 章:并发——安全地多线程
Rust 的并发是建立在所有权和类型系统上的,许多并发错误在编译时就被干掉了。
-
线程:
thread::spawn创建新线程,闭包里是要执行的代码。join等待线程结束。 -
消息传递:通过通道(
mpsc::channel)在线程间传递数据,tx.send()发送,rx.recv()接收。所有权随数据转移,不会发生竞争。 -
共享状态:用
Arc<Mutex<T>>(原子引用计数+互斥锁)来在多个线程间安全地共享可变数据。Arc相当于线程安全的Rc,Mutex保证一次只有一个线程能修改数据。use std::sync::{Arc, Mutex};
use std::thread;let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}for handle in handles {
handle.join().unwrap();
}println!("结果: {}", *counter.lock().unwrap()); // 10
Rust 的类型系统保证你每次使用锁的时候都必须先获取锁,不会忘记。
第 11 章:下一步学什么?
到这里,你已经把 Rust 大部分核心概念过了一遍。接下来可以:
- 写小项目:比如命令行工具(用
clap库解析参数)、小游戏、简单的 HTTP 服务器(actix-web或axum)。 - 看经典书:《The Rust Programming Language》(Rust 圣经),完全免费,有中文版。
- 刷题:在 Exercism、LeetCode 上用 Rust 解题,强化语法。
- 深入阅读:学习
unsafeRust、异步编程、宏等高级主题。 - 读代码:去 GitHub 上找 Rust 项目,看别人怎么组织代码、用 trait 抽象。
记住,Rust 刚开始会觉得被编译器“束缚”,但等你尝到一次编译通过就能稳定运行的甜头时,就会爱上这种感觉。编译器不是敌人,是你最好的老师和搭档。
祝你在 Rust 的海洋里玩得开心!

浙公网安备 33010602011771号