环境搭建与第一个 Rust 程序

今日教学目标

  1. 完成 Rust 工具链(rustup、rustc、cargo)安装并验证版本。
  2. 安装并熟悉 RustRover IDE:创建项目、运行程序、查看项目结构。
  3. 理解 Rust 项目基本结构:Cargo.tomlsrc/main.rs
  4. 编写第一个 Hello, world! 程序,体验编译型语言工作流。
  5. 理解 Rust 语言的设计哲学:安全、高性能、无畏并发,以及它为什么值得学习。

为什么要学 Rust?

在正式动手之前,先聊聊 Rust 到底好在哪里。

编译器是你最好的搭档

Rust 的编译器不仅仅帮你找错误,它还会主动推荐正确的写法。比如下面这段代码,启动了一个新线程引用当前线程的变量:

1
2
3
4
let name = "xiu".to_string();
std::thread::spawn(|| {
println!("hello {}", name);
});

这段代码隐含着线程不安全的访问——当前线程持有的变量 name 可能在新线程使用之前就被释放,发生 use-after-free 错误。Rust 编译器不仅能在编译期检测出这个错误,告诉你原因”may outlive borrowed value”,还会进一步推荐你加入 move 关键字来解决。它甚至给出 rustc --explain E0373 命令让你查看详细的错误解释。

这种体验,就好像你旁边坐着一个和蔼可亲的大牛,在孜孜不倦地帮你审阅代码。一旦你适应了,就很难离开。

image-20260817154225078

image-20260817154235414

性能与表达力兼得

Rust 编译出的程序运行速度和 C/C++ 是一个级别的,同时它的使用体验又像 Python/TypeScript 这样的高级语言,表达能力一流。截止 2021 年,主流互联网公司纷纷把 Rust 纳入主力语言——Dropbox 用 Rust 重写文件同步引擎、Discord 用 Rust 重写状态服务。Discord 原先使用 Golang 的状态服务,一来消耗大量内存,二来在高峰期因为垃圾回收导致巨大延迟,换成 Rust 后不仅性能提升,还让代码重构变得举重若轻。

无畏并发

Rust 的所有权系统让你在写多线程代码时,编译器会帮你检查数据竞争问题。其他语言中被标榜为实践典范的并发手段,在 Rust 这里只不过是一种并发工具。Rust 提供了从 atomics 到 Mutex、Semaphore、Channel,再到 actor model 的全套并发原语,同时还能保证并发安全。

学习曲线:先苦后甜

Rust 被公认是一门难学的语言。它的核心难点是变量的所有权和生命周期——这是几乎所有其他编程语言都未曾涉及的领域。但一旦迈过这段艰难的思维转换期,其他的知识点就是所有权和生命周期概念在不同领域的具体使用。

学习 Rust 的方法可以总结为精准学习 + 刻意练习:深挖一个个表层知识点,回归底层基础知识的本原,再一层层往上构建知识体系。就像撒一层土,夯实,再撒一层。

💡 类比理解

  • rustc 是 Rust 的”翻译官”,把 Rust 代码翻译成机器能运行的二进制文件
  • cargo 是 Rust 的”项目经理”,负责管理依赖、编译、测试、运行
  • rustup 是”版本管家”,负责安装、切换、升级 Rust 版本

如果没有安装,访问 https://www.rust-lang.org/zh-CN/tools/install 下载 rustup-init.exe,按提示安装即可。安装完成后重新打开 PowerShell 再验证。

在 UNIX 系统下(如 macOS/Linux),可以直接运行:

1
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

这会在你的系统上安装 Rust 工具链。

安装与启动 RustRover

  1. 从 JetBrains 官网下载并安装 RustRover(学生可申请免费教育授权)。
  2. 打开 RustRover,第一次启动会让你选择主题和导入设置,按喜好选择即可。

创建第一个 Rust 项目

按照以下步骤操作:

  1. 点击菜单栏 File → New Project
  2. 在左侧选择 Rust
  3. 右侧配置:
    • Name:输入项目名,比如 day01_hello
    • Location:选择项目存放路径
    • Toolchain:选择系统已安装的 rustup(一般会自动检测到)
    • Standard library:勾选 Download via rustup,这样以后可以跳转到标准库源码
    • Project template:选择 Binary,表示生成一个可执行程序(有 main 函数)
  4. 点击 Create,稍等片刻,项目就创建好了

💡 类比理解:选择 Binary 就像告诉工厂”我要生产一台能直接开的汽车”;如果选择 Library,则是”我要生产一批零件,给别人装配用”。

认识项目结构

创建后,左侧 Project 视图(按 Alt+1 可显示/隐藏)会看到:

1
2
3
4
5
day01_hello/
├── Cargo.toml ← 项目配置和依赖清单
├── src/
│ └── main.rs ← 程序入口
└── target/ ← 编译产物(Debug/Release 都在这,首次编译后出现)
  • Cargo.toml:相当于项目的”身份证 + 采购清单”,里面写项目名称、版本、作者、依赖等信息。它符合 TOML 格式。
  • src/main.rs:程序的入口文件,相当于”大门”。程序从这里开始执行。
  • target/:编译后生成的文件都在这里。刚开始没有,第一次编译后出现。

运行程序

有三种方式运行:

方式 1:点击绿色三角按钮

  • 打开 src/main.rs,在 fn main() 左侧会出现一个绿色的小三角
  • 点击它,选择 Run 'Run day01_hello',程序就会编译并运行
  • 运行结果会出现在底部 Run 工具窗口(按 Alt+4 可切换)

方式 2:右键菜单

  • src/main.rs 文件上右键,选择 Run 'day01_hello'

方式 3:Terminal 手动执行

  • 打开底部 Terminal 标签(按 Alt+F12 打开/关闭)
  • 输入:
    1
    cargo run
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

> 💡 **快捷键速查**(Windows/Linux;macOS 大体将 `Ctrl` 换成 `⌘`、`Alt` 换成 `⌥`,详见 RustRover 的 *Settings → Keymap → macOS* 方案):
> | 快捷键 | 作用 |
> |--------|------|
> | `Alt+1` | 显示/隐藏 Project 视图 |
> | `Alt+4` | 显示/隐藏 Run 工具窗口 |
> | `Alt+F12` | 打开/关闭 Terminal |
> | `Ctrl+Shift+A` | 搜索任意动作或设置 |
> | `Ctrl+B` | 跳转到定义(函数、结构体等) |
> | `Ctrl+/` | 注释/取消注释当前行 |
>
> 后续每天的小节里只列出当天特有的操作提示,通用快捷键以本表为准。

### 常见 Cargo 命令

| 命令 | 作用 | 类比理解 |
|------|------|----------|
| `cargo new 项目名` | 创建新项目 | 给新项目搭好脚手架 |
| `cargo build` | 编译项目 | 把代码翻译成可执行文件 |
| `cargo run` | 编译并运行 | build + run 二合一 |
| `cargo check` | 只检查能否编译,不生成二进制 | 快速"体检",比 build 快很多 |
| `cargo clean` | 删除 target 目录 | 清空编译产物 |

### 常见错误提示

1. **`error: could not find Cargo.toml in ...`**
- 原因:你在错误的目录下执行了 `cargo run`
- 解决:用 `cd` 切换到项目根目录(即包含 `Cargo.toml` 的目录),再执行

2. **`linker link.exe not found`**
- 原因:Windows 缺少 MSVC 构建工具
- 解决:安装 Visual Studio 的 C++ 桌面开发工作负载,或改用 GNU 工具链:`rustup default stable-x86_64-pc-windows-gnu`

3. **`unresolved import` 或红色波浪线**
- 原因:RustRover 还没索引完项目,或依赖没有下载
- 解决:等待右下角索引进度完成,或点击 Cargo 面板右上角的刷新按钮

---

## 语法讲解

### `fn main()` 是程序入口

每个 Rust 可执行程序都必须有一个 `main` 函数,程序从这里开始执行:

​```rust
// src/main.rs
// 这一行定义了一个叫 main 的函数
// fn 是 function 的缩写,和很多语言一样
fn main() {
// 函数体用一对花括号 {} 包起来
// 下面这行向控制台打印一行文字
println!("Hello, world!");
}

💡 类比理解main 函数就像一本书的”第一章第一页”,读者(操作系统)打开书后,自动从这里开始读。

Rust 的整体语法偏 C/C++ 风格:

  • 函数体用花括号 {} 包裹
  • 表达式之间用分号 ; 分隔
  • 访问结构体的成员函数或变量使用点 . 运算符
  • 访问命名空间或对象的静态函数使用双冒号 :: 运算符
  • 可执行体的入口函数是 main()

💡 初学者提示:现在不需要深入理解宏的原理,只要记住:带 ! 的是宏,不带的是函数。宏的原理会在 Day 29 详细讲解。

println! 是宏,不是普通函数

注意 println! 后面有个感叹号 !。在 Rust 中,带 ! 的是宏(macro),不是普通函数。

1
2
3
4
5
6
7
8
9
fn main() {
// 宏调用,向标准输出打印一行文字
println!("Hello, RustRover!");

// println! 可以接受任意个数的参数
// 因为 Rust 是强类型语言,函数的类型需要在编译期敲定
// 而宏可以在编译时展开成具体的代码,所以能接受可变参数
println!("我叫 {},今年 {} 岁", "Rust", 18); // 占位符格式化输出
}
  • 普通函数调用:函数名(参数)
  • 宏调用:宏名!(参数)

💡 类比理解:普通函数就像一台固定程序的咖啡机,只能做固定口味的咖啡;宏更像一台”能根据你的指令现场改装自己的咖啡机”,它在编译时把代码展开成具体的函数调用。Rust 支持声明宏和过程宏(包含派生宏、属性宏、函数宏),println! 是用 macro_rules! 定义的声明宏。

Rust 是 AOT 编译型语言

AOT = Ahead Of Time,也就是”提前编译”。你写的 Rust 代码需要先经过编译器处理,生成机器码,然后才能运行。

这和 Python、JavaScript 等解释型语言不同——它们通常是边读边执行。

💡 类比理解:AOT 编译就像把整本外语书提前翻译成中文书,读者读中文书时很快;解释型语言就像请一个翻译官在边上逐句翻译,灵活性高但运行时慢一些。

Rust 的核心语言特点

通过第一个程序,我们可以感受到 Rust 的一些基本特点:

  1. 使用 cargo 管理项目:类似 Node.js 的 npm、Golang 的 go,用来做依赖管理和开发过程中的任务管理(编译、运行、测试、格式化等)。

  2. 强类型语言,但支持类型推导:写代码时的直观感受和写脚本语言差不多。编辑器中安装了 rust-analyzer 插件后,变量的类型会自动提示出来。

  3. 变量默认不可变:如果要修改变量的值,需要显式地使用 mut 关键字。这符合最小权限原则,有助于写出健壮的代码。

  4. 绝大多数代码都是表达式if/while/for/loop 都会返回一个值,函数最后一个表达式就是返回值,这和函数式编程语言一致。

  5. 支持宏编程:很多基础功能如 println!() 都被封装成宏,让开发者写出简洁的代码。

  6. 支持面向接口编程和泛型编程

  7. 有非常丰富的数据类型和强大的标准库

Cargo.toml 关键字段

打开 Cargo.toml,你会看到类似下面的内容:

1
2
3
4
5
6
[package]
name = "day01_hello"
version = "0.1.0"
edition = "2021"

[dependencies]
  • [package]:表示下面是关于这个包本身的配置
  • name:包名,通常也是生成的可执行文件名
  • version:版本号,遵循”语义化版本”规范,格式为 主版本.次版本.修订号
  • edition:Rust 语言版本。当前常用 "2021",就像 Java 的 Java 8、Java 17 一样,不同 edition 语法略有差异
  • [dependencies]:依赖列表。以后要使用第三方库时,就写在这里

💡 类比理解Cargo.toml 就像一份餐厅的菜单和营业执照——菜单(dependencies)告诉顾客你能吃到什么菜,营业执照(package)说明你叫什么名字、是什么版本。一个 Rust 项目也被称为一个 crate,crate 可以是可执行项目,也可以是一个库。

Cargo 工作流

开发时的典型工作流如下:先用 cargo check 快速检查代码能否编译(不生成二进制文件,速度最快),如果有错就修改代码;确认无误后用 cargo build 编译,或直接用 cargo run 编译并运行。三者形成一个高效的开发循环。


第一个实用 Rust 程序:网页抓取

Hello World 太简单了,让我们来写一个稍有实际意义的程序——通过 HTTP 请求 Rust 官网首页,把获得的 HTML 转换成 Markdown 保存起来。用 JavaScript 或 Python 可能只需要十多行代码,我们看看 Rust 怎么处理。

首先,用 cargo new scrape_url 生成一个新项目。在 Cargo.toml 中加入依赖:

1
2
3
[dependencies]
reqwest = { version = "0.12", features = ["blocking"] }
html2md = "0.2"

然后,在 src/main.rs 中编写代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 引入标准库的 fs 模块,用于文件操作
use std::fs;

fn main() {
// 定义要抓取的 URL 和输出文件名
let url = "https://www.rust-lang.org/";
let output = "rust.md";

println!("Fetching url: {}", url);
// 发送 HTTP GET 请求,获取响应体文本
// unwrap() 表示如果出错就直接终止程序
let body = reqwest::blocking::get(url).unwrap().text().unwrap();

println!("Converting html to markdown...");
// 将 HTML 转换为 Markdown
let md = html2md::parse_html(&body);

// 将 Markdown 内容写入文件
fs::write(output, md.as_bytes()).unwrap();
println!("Converted markdown has been saved in {}.", output);
}

运行 cargo run,在一段略微漫长的编译后,程序开始运行:

1
2
3
Fetching url: https://www.rust-lang.org/
Converting html to markdown...
Converted markdown has been saved in rust.md.

当前目录下会生成一个 rust.md 文件,内容就是 Rust 官网主页的 Markdown 版本。Bingo!第一个 Rust 程序运行成功!

从这个例子中,我们可以感受到:

  • Rust 虽然是强类型语言,但编译器支持类型推导,写代码的直观感受和脚本语言差不多
  • use 关键字可以简化对命名空间内部函数或数据类型的引用
  • 错误处理使用 Result<T, E> 类型unwrap() 是简化写法,只关心成功的结果

⚠️ 避坑指南:第一次编译会比较慢,因为 cargo 需要下载并编译所有依赖。后续修改代码后重新编译会快很多,因为 cargo 只会重新编译改动过的部分。


常见错误 / 避坑指南

1. 在错误的目录运行 cargo

1
error: could not find `Cargo.toml` in `.` or any parent directory

原因:你不在包含 Cargo.toml 的目录中。
解决cd 到项目根目录再执行。

2. 链接器缺失

1
error: linker `link.exe` not found

原因:Windows 上缺少 MSVC 构建工具。
解决:安装 Visual Studio Build Tools,勾选”C++ 桌面开发”工作负载。

3. RustRover 红色波浪线不消失

原因:rust-analyzer 还在索引项目,或依赖尚未下载完成。
解决:等待右下角进度条完成;如果卡住,点击 Cargo 工具窗口的刷新按钮。

4. 编译时间过长

原因:第一次编译需要下载并编译所有依赖 crate。
解决:这是正常的。后续增量编译会快很多。可以使用 cargo check 代替 cargo build 来快速检查代码。

💡 类比理解:第一次编译就像第一次做一道复杂的菜——你要去超市买齐所有食材和调料。之后只要食材还有库存,再做就快了。


🔒 安全视角延伸

缓冲区溢出:C/C++ 的阿喀琉斯之踵

在正式踏入 Rust 的安全世界之前,让我们先了解一个困扰 C/C++ 程序员数十年的梦魇——缓冲区溢出(Buffer Overflow)

什么是缓冲区溢出攻击?

程序在运行时会在栈上为局部变量分配固定大小的内存空间(缓冲区)。如果程序向缓冲区写入数据时不检查长度,攻击者就可以输入超出缓冲区容量的数据,多余的字节会覆盖相邻的内存区域——包括栈帧中存储的返回地址

当函数执行完毕、CPU 准备返回时,它会读取被覆盖的返回地址,跳转到攻击者指定的位置执行恶意代码(shellcode)。这就是经典的栈溢出攻击(Stack Smashing)。

C 语言 vs Rust:代码对比

下面是一段典型的 C 语言漏洞代码:

1
2
3
4
5
6
7
8
9
10
11
// vuln.c — 演示缓冲区溢出漏洞(请勿在生产环境使用)
#include <string.h>
#include <stdio.h>

void greet(char *name) {
// name 被复制到 8 字节的栈缓冲区
// strcpy 不检查长度!如果 name 超过 8 字节就会溢出
char buf[8];
strcpy(buf, name); // 危险:无边界检查
printf("Hello, %s!\n", buf);
}

strcpy 函数会将 name 的所有字节拷贝到 buf 中,不检查目标缓冲区的大小。如果 name 超过 8 字节,多出的数据就会覆盖栈帧中的返回地址。

等价的 Rust 代码则是完全安全的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// safe.rs — Rust 版本,编译运行均安全

fn greet(name: &str) {
// 创建一个 8 字节的数组作为缓冲区
let mut buf = [0u8; 8];

// 尝试将 name 的字节拷贝到 buf 中
// 如果 name 太长,这里会在运行时 panic,而不是默默溢出
let bytes = name.as_bytes();
if bytes.len() <= buf.len() {
// 长度合法,安全拷贝
buf[..bytes.len()].copy_from_slice(bytes);
} else {
// 长度超出缓冲区容量,优雅地处理错误
// 而不是像 C 那样默默溢出!
println!("名字太长了,最多 {} 字节", buf.len());
return;
}

// 安全地打印
let name_str = std::str::from_utf8(&buf[..name.len()]).unwrap();
println!("Hello, {}!", name_str);
}

fn main() {
greet("Alice"); // 正常输出
greet("Alicexxxxxxxxxx"); // 安全拒绝,不会溢出
}

Rust 在两个层面阻止缓冲区溢出:

  1. 编译期:所有权和借用检查器确保不存在悬垂指针和越界访问的模式。
  2. 运行期:数组/切片访问会进行边界检查(bounds check)。如果索引越界,程序会立即 panic 并安全退出,而不是默默写入非法内存。

安全税:微小的代价,巨大的回报

有人可能会担心边界检查带来的性能开销。实际上,现代 CPU 的分支预测器对边界检查的命中率极高(绝大多数访问都是合法的),开销通常只有 1-2 条指令。这就像超市入口的安检——大多数顾客都不会触发警报,但它能在关键时刻阻止危险物品进入。

Rust 用这微小的运行时开销,彻底免疫了缓冲区溢出、越界读写、use-after-free 等整类内存安全漏洞。根据微软安全响应中心的数据,约 70% 的安全漏洞属于内存安全问题。Rust 从语言层面消灭了这些漏洞的生存土壤。

📖 延伸阅读OWASP Buffer Overflow — 了解更多关于缓冲区溢出攻击的原理与防护策略。


课后实验作业

作业 1:创建并运行 Hello World

  1. 在 RustRover 中创建新项目 day01_hello
  2. 修改 main.rs,让程序输出:
    1
    2
    Hello, Rust!
    I am learning Rust from the Rust Book.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
3. 在 Terminal 中分别执行 `cargo build`、`cargo run`、`cargo check`,观察差异

### 作业 2:Cargo.toml 初探
1. 修改 `Cargo.toml` 中的 `version` 为 `0.1.1`,重新运行
2. 查看 `target/debug` 目录下生成的可执行文件名

### 作业 3:格式化输出练习
- 使用 `println!` 的占位符 `{}` 和 `{:?}` 分别打印一个字符串和一个整数
- 尝试使用 `{:>5}`、`{:<5}`、`{:^5}` 等格式化语法对齐输出

### 作业 4(大实验种子):项目初始化
- 新建大实验根项目 `pi_agent`(Binary)
- 在 `Cargo.toml` 中预留未来依赖位置(后续会陆续添加)
- 在 `main.rs` 中仅打印一行:`Pi Agent initialized.`,确保能编译运行

---

## 参考答案

### 作业 1 答案

​```rust
// day01_hello/src/main.rs

// main 函数是程序入口
fn main() {
// 第一行输出问候语
println!("Hello, Rust!");
// 第二行输出学习说明
println!("I am learning Rust from the Rust Book.");
}

运行方式及差异说明:

  • cargo check:只检查代码能否编译,不生成可执行文件,速度最快
  • cargo build:编译并生成 target/debug/day01_hello.exe
  • cargo run:编译(如果需要)并直接运行,最方便

作业 2 答案

修改后的 Cargo.toml

1
2
3
4
5
6
[package]
name = "day01_hello"
version = "0.1.1" # 版本号从 0.1.0 改成 0.1.1
edition = "2021"

[dependencies]

在 Windows 上,执行 cargo build 后,可执行文件位于:

1
target/debug/day01_hello.exe

可以直接在 Terminal 中输入 ./target/debug/day01_hello.exe 运行。

作业 3 答案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// day01_hello/src/main.rs

fn main() {
// {} 是最常用的占位符
let name = "Rust";
let version = 2021;
println!("语言: {}, 版本: {}", name, version);

// {:?} 是 Debug 格式,适合调试输出
println!("Debug 格式: {:?}, {:?}", name, version);

// 对齐输出::>5 表示右对齐宽度5,:<5 左对齐,:^5 居中
println!("右对齐: {:>5}", 42);
println!("左对齐: {:<5}|", 42);
println!("居中: {:^5}|", 42);

// 补零对齐
println!("补零: {:05}", 42);
}

作业 4 答案(大实验种子)

Cargo.toml

1
2
3
4
5
6
7
8
9
10
[package]
name = "pi_agent"
version = "0.1.0"
edition = "2021"

[dependencies]
# 未来会在这里添加依赖,例如:
# reqwest = "0.12" # HTTP 请求
# serde = "1.0" # JSON 序列化
# tokio = "1" # 异步运行时

src/main.rs

1
2
3
4
5
6
7
// pi_agent/src/main.rs

// main 函数是程序入口
fn main() {
// 打印初始化信息,确认项目能正常编译运行
println!("Pi Agent initialized.");
}

今日小结

  • Rust 是一门兼顾性能、安全、表达力的系统级编程语言,编译器会像大牛一样帮你审阅代码并推荐修复方案。
  • rustuprustccargo 是 Rust 的三大核心工具。
  • RustRover 是 JetBrains 出品的 Rust IDE,能大幅提升开发效率。
  • Cargo.toml 是项目的”身份证 + 采购清单”,src/main.rs 是程序入口。
  • cargo check 快速检查,cargo build 编译,cargo run 编译并运行。
  • 我们今天种下了 pi_agent 大实验的种子,后面的每一天都会往里面添砖加瓦。
  • Rust 的学习曲线虽然陡峭,但只要迈过所有权的思维转换期,就能体会到”越用越享受”的编程体验。