Rust 学习笔记

基于 Rust 官方教程的学习笔记,包含详细的代码示例和要点总结

Rust 程序设计语言 学习笔记

文档信息


核心摘要

《Rust 程序设计语言》是 Rust 官方教程的中文翻译版。本笔记覆盖前五章内容,从环境搭建到核心概念(所有权、结构体)。Rust 是一门注重安全性、并发性和性能的系统编程语言,其独特的所有权系统在编译时就能防止内存错误和数据竞争。


第一章:入门指南

1.1 安装 Rust

核心工具: rustup - Rust 版本管理器

安装命令:

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

# Windows: 访问官网下载安装器

常用命令:

  • rustc --version - 查看版本
  • rustup update - 更新 Rust
  • rustup doc - 本地文档

1.2 Hello, World!

fn main() {
    println!("Hello, world!");
}

要点:

  • fn main() 是程序入口
  • println! 是宏(带 !),不是函数
  • 语句以分号 ; 结尾
  • Rust 是预编译语言 (AOT)

1.3 Cargo

Cargo 是 Rust 的构建系统和包管理器。

常用命令:

cargo new project_name  # 创建项目
cargo build            # 编译
cargo run              # 编译并运行
cargo check            # 快速检查(不生成可执行文件)
cargo build --release  # 发布优化编译

项目结构:

project/
├── Cargo.toml    # 配置文件
└── src/
    └── main.rs   # 源代码

第二章:猜数游戏

通过实战项目学习 Rust 基础。

核心概念

概念 说明
let mut 声明可变变量
String::new() 创建空字符串
io::stdin().read_line() 读取用户输入
Result 类型 错误处理
match 表达式 模式匹配
外部 crate 使用 rand 生成随机数

关键代码片段

use std::io;
use std::cmp::Ordering;
use rand::Rng;

let secret_number = rand::thread_rng().gen_range(1..=100);

let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("读取失败");

let guess: u32 = guess.trim().parse().expect("请输入数字");

match guess.cmp(&secret_number) {
    Ordering::Less => println!("太小了"),
    Ordering::Greater => println!("太大了"),
    Ordering::Equal => println!("正确!"),
}

第三章:常见编程概念

3.1 变量与可变性

默认不可变:

let x = 5;      // 不可变
let mut y = 5;  // 可变

常量:

const MAX_POINTS: u32 = 100_000;  // 必须标注类型

遮蔽 (Shadowing):

let x = 5;
let x = x + 1;  // 新变量,可以改变类型

3.2 数据类型

标量类型:

类型 说明 示例
整数 i8/u8 到 i128/u128 let a: i32 = 42;
浮点 f32, f64 (默认) let b = 3.14;
布尔 bool let c = true;
字符 char (Unicode) let d = '中';

复合类型:

// 元组 - 固定长度,可不同类型
let tup: (i32, f64, u8) = (500, 6.4, 1);
let (x, y, z) = tup;  // 解构
let first = tup.0;    // 索引访问

// 数组 - 固定长度,相同类型,栈分配
let arr = [1, 2, 3, 4, 5];
let arr = [3; 5];  // [3, 3, 3, 3, 3]

3.3 函数

fn add(x: i32, y: i32) -> i32 {
    x + y  // 表达式,无分号,作为返回值
}

3.4 控制流

if 表达式:

let number = if condition { 5 } else { 6 };

循环:

// loop - 无限循环
let result = loop {
    if done { break value; }  // 可返回值
};

// while
while condition { }

// for - 最常用
for element in collection { }
for i in (1..4).rev() { }  // 3, 2, 1

第四章:认识所有权

所有权规则(核心)

  1. 每个值都有一个所有者(变量)
  2. 同一时刻只能有一个所有者
  3. 所有者离开作用域时,值被丢弃

移动 vs 克隆

// 移动 (Move) - 堆数据
let s1 = String::from("hello");
let s2 = s1;  // s1 失效!

// 克隆 (Clone) - 深拷贝
let s1 = String::from("hello");
let s2 = s1.clone();  // s1 仍有效

// 复制 (Copy) - 栈数据(整数、布尔、浮点、字符、纯Copy元组)
let x = 5;
let y = x;  // x 仍有效

引用与借用

// 不可变引用 - 可以有多个
let s = String::from("hello");
let r1 = &s;
let r2 = &s;

// 可变引用 - 同一时刻只能有一个
let mut s = String::from("hello");
let r = &mut s;

借用规则:

  • 任意时刻,只能有一个可变引用 多个不可变引用
  • 引用必须总是有效的(无悬垂引用)

切片

let s = String::from("hello world");
let hello = &s[0..5];   // "hello"
let world = &s[6..11];  // "world"
let slice = &s[..];     // 整个字符串

第五章:使用结构体

定义结构体

struct User {
    username: String,
    email: String,
    active: bool,
    sign_in_count: u64,
}

// 实例化
let user = User {
    email: String::from("test@example.com"),
    username: String::from("test"),
    active: true,
    sign_in_count: 1,
};

// 字段简写
fn build_user(email: String, username: String) -> User {
    User { email, username, active: true, sign_in_count: 1 }
}

// 结构体更新语法
let user2 = User { email: String::from("new@example.com"), ..user };

元组结构体与单元结构体

struct Color(i32, i32, i32);  // 元组结构体
struct AlwaysEqual;           // 单元结构体

方法与关联函数

struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    // 方法 - 第一个参数是 &self
    fn area(&self) -> u32 {
        self.width * self.height
    }

    // 关联函数 - 无 self,用 :: 调用
    fn square(size: u32) -> Self {
        Self { width: size, height: size }
    }
}

let rect = Rectangle { width: 30, height: 50 };
let area = rect.area();              // 方法调用
let sq = Rectangle::square(10);      // 关联函数调用

知识要点总结

  1. Rust 是预编译语言,编译后可直接分发
  2. 变量默认不可变,需 mut 才能修改
  3. 所有权系统是 Rust 内存安全的核心
  4. 借用规则防止数据竞争
  5. 模式匹配 (match) 是处理枚举和错误的利器
  6. Cargo 是标准的项目管理工具

延伸阅读

rustup - Rust版本管理工具

rustup 是 Rust 官方提供的命令行工具,用于管理 Rust 版本和相关工具链。它是安装 Rust 的推荐方式。

要点

  • 跨平台支持(Linux、macOS、Windows)
  • 可以安装多个 Rust 版本并在它们之间切换
  • 自动管理 rustc、cargo 等工具

常用命令

# 安装(Linux/macOS)
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh

# 查看版本
rustc --version

# 更新
rustup update

# 卸载
rustup self uninstall

# 打开本地文档
rustup doc

相关笔记

  • [[20260117002]] - Cargo 构建工具(前置:安装后自动包含)
  • [[20260117003]] - Hello World 程序(扩展:安装后的第一个程序)

来源

Rust 安装指南

Cargo - Rust构建系统和包管理器

Cargo 是 Rust 的官方构建系统和包管理器。它负责构建代码、下载依赖库并编译它们。

要点

  • 随 rustup 一起安装
  • 统一的项目结构和配置
  • 跨平台命令一致

核心命令

命令 作用
cargo new name 创建新项目
cargo build 编译项目(debug模式)
cargo run 编译并运行
cargo check 快速检查,不生成可执行文件
cargo build --release 发布优化编译

项目结构

project/
├── Cargo.toml    # 项目配置(TOML格式)
├── Cargo.lock    # 依赖锁定文件
├── src/
│   └── main.rs   # 源代码入口
└── target/       # 编译输出目录

相关笔记

  • [[20260117001]] - rustup 安装工具(前置:Cargo 包含在内)
  • [[20260117004]] - Cargo.toml 配置文件(扩展:详细配置说明)

来源

Hello, Cargo!

Hello World - Rust程序基本结构

每个 Rust 程序都从 main 函数开始执行。这是程序的入口点。

代码示例

fn main() {
    println!("Hello, world!");
}

要点

  • fn 关键字用于定义函数
  • main 是特殊函数,程序入口
  • println!(带 !),不是普通函数
  • 语句以分号 ; 结尾
  • 使用 4 个空格缩进(非 Tab)

Rust 是预编译语言

rustc main.rs   # 编译
./main          # 运行

编译后的可执行文件可以在没有 Rust 环境的机器上运行(AOT 编译)。

相关笔记

  • [[20260117005]] - 函数定义(扩展:完整函数语法)
  • [[20260117006]] - 宏与函数的区别(扩展:println! 的本质)

来源

Hello, World!

match 表达式 - 模式匹配

match 是 Rust 强大的控制流结构,用于将值与一系列模式进行比较。

基本语法

match value {
    pattern1 => expression1,
    pattern2 => expression2,
    _ => default_expression,  // 通配符,匹配所有
}

要点

  • 穷尽性:必须覆盖所有可能的值
  • _ 通配符匹配任意值
  • 每个分支是一个表达式,可返回值
  • 分支使用 => 而非 :

代码示例

use std::cmp::Ordering;

match guess.cmp(&secret_number) {
    Ordering::Less => println!("太小了"),
    Ordering::Greater => println!("太大了"),
    Ordering::Equal => {
        println!("正确!");
        break;
    }
}

// 返回值
let result = match x {
    1 => "one",
    2 => "two",
    _ => "other",
};

// 处理 Result
let number: u32 = match "42".parse() {
    Ok(num) => num,
    Err(_) => 0,  // 忽略错误详情
};

相关笔记

  • [[20260117009]] - 控制流(前置:if 表达式)
  • [[20260117011]] - 所有权规则(扩展:模式匹配与所有权)

来源

猜数游戏

变量与可变性

Rust 中变量默认不可变(immutable)。这是 Rust 保证安全性和并发性的重要设计。

要点

  • 默认不可变,需显式使用 mut 才能修改
  • 不可变变量不同于常量
  • 编译器会检查不可变变量的修改

代码示例

// 不可变变量
let x = 5;
// x = 6;  // 错误!不能修改

// 可变变量
let mut y = 5;
y = 6;  // 正确

// 常量(必须标注类型)
const MAX_POINTS: u32 = 100_000;

变量 vs 常量

特性 变量 (let) 常量 (const)
可变性 可选 mut 永远不可变
类型标注 可选 必须
作用域 任意 任意(含全局)
值来源 运行时 编译时常量表达式

相关笔记

  • [[20260117007]] - 遮蔽 Shadowing(扩展:重新声明同名变量)
  • [[20260117008]] - 数据类型(扩展:类型系统)

来源

变量与可变性

遮蔽 (Shadowing)

在 Rust 中,可以用相同的名字声明新变量,新变量会遮蔽之前的变量。

要点

  • 使用 let 重新声明同名变量
  • 创建的是新变量,不是修改旧变量
  • 可以改变类型(与 mut 的关键区别)
  • 遮蔽是有作用域的

代码示例

let x = 5;
let x = x + 1;        // x = 6,新变量
let x = x * 2;        // x = 12,又一个新变量

// 可以改变类型
let spaces = "   ";           // 字符串类型
let spaces = spaces.len();    // 数字类型,合法!

// mut 不能改变类型
let mut spaces = "   ";
// spaces = spaces.len();     // 错误!类型不匹配

遮蔽 vs 可变

特性 遮蔽 (let) 可变 (mut)
本质 创建新变量 修改现有变量
改变类型 可以 不可以
关键字 每次用 let 声明时用 mut

相关笔记

  • [[20260117004]] - 变量与可变性(前置:理解 let 和 mut)

来源

变量与可变性

标量数据类型

标量类型代表单个值。Rust 有四种基本标量类型。

四种标量类型

1. 整数类型

长度 有符号 无符号
8-bit i8 u8
16-bit i16 u16
32-bit i32 (默认) u32
64-bit i64 u64
128-bit i128 u128
arch isize usize

2. 浮点类型

let x = 2.0;      // f64(默认,双精度)
let y: f32 = 3.0; // f32(单精度)

3. 布尔类型

let t = true;
let f: bool = false;

4. 字符类型

let c = 'z';
let heart = '❤';
let chinese = '中';  // Unicode 支持
  • 用单引号
  • 4 字节大小
  • 支持 Unicode 标量值

相关笔记

  • [[20260117007]] - 复合数据类型(扩展:元组和数组)
  • [[20260117004]] - 变量与可变性(前置:类型声明)

来源

数据类型

复合数据类型

复合类型可以将多个值组合成一个类型。Rust 有两种原生复合类型:元组和数组。

元组 (Tuple)

  • 固定长度,一旦声明不能增减
  • 可以包含不同类型
  • 通过解构或索引访问
// 声明
let tup: (i32, f64, u8) = (500, 6.4, 1);

// 解构
let (x, y, z) = tup;

// 索引访问(从 0 开始)
let first = tup.0;
let second = tup.1;

// 单元元组
let unit: () = ();  // 空值/空返回类型

数组 (Array)

  • 固定长度
  • 所有元素类型相同
  • 分配在栈上(非堆)
  • 越界访问会 panic
// 声明
let arr = [1, 2, 3, 4, 5];

// 指定类型和长度
let arr: [i32; 5] = [1, 2, 3, 4, 5];

// 初始化相同值
let arr = [3; 5];  // [3, 3, 3, 3, 3]

// 访问
let first = arr[0];

元组 vs 数组

特性 元组 数组
类型 可不同 必须相同
长度 固定 固定
访问 .0 .1 [0] [1]
用途 组合不同类型 同类型集合

相关笔记

  • [[20260117006]] - 标量数据类型(前置:基本类型)
  • [[20260117015]] - 结构体(扩展:更复杂的复合类型)

来源

数据类型

函数定义与返回值

函数使用 fn 关键字定义。Rust 使用 snake_case 命名规范。

基本语法

fn function_name(param1: Type1, param2: Type2) -> ReturnType {
    // 函数体
}

要点

  • 参数必须声明类型
  • 返回类型用 -> 指定
  • 最后一个表达式(无分号)自动作为返回值
  • 也可用 return 提前返回

代码示例

// 无参数无返回值
fn say_hello() {
    println!("Hello!");
}

// 有参数
fn greet(name: &str) {
    println!("Hello, {}!", name);
}

// 有返回值
fn add(x: i32, y: i32) -> i32 {
    x + y  // 表达式,无分号,作为返回值
}

// 使用 return
fn early_return(x: i32) -> i32 {
    if x < 0 {
        return 0;  // 提前返回
    }
    x * 2
}

语句 vs 表达式

类型 说明 返回值
语句 执行操作,以分号结尾
表达式 计算并返回值,无分号
let y = {
    let x = 3;
    x + 1  // 表达式,返回 4
};  // y = 4

相关笔记

  • [[20260117003]] - Hello World(前置:main 函数)
  • [[20260117009]] - 控制流(扩展:if 表达式)
  • [[20260117016]] - 方法与关联函数(扩展:结构体方法)

来源

函数

控制流 - if表达式和循环

Rust 的控制流结构包括 if 表达式和三种循环。

if 表达式

let number = 6;

if number % 2 == 0 {
    println!("偶数");
} else if number % 3 == 0 {
    println!("能被3整除");
} else {
    println!("其他");
}

// if 是表达式,可以返回值
let result = if condition { 5 } else { 6 };

注意:条件必须是 bool 类型,不会自动转换。

三种循环

loop - 无限循环

let result = loop {
    counter += 1;
    if counter == 10 {
        break counter * 2;  // 可以返回值
    }
};

while - 条件循环

while number != 0 {
    println!("{}", number);
    number -= 1;
}

for - 遍历循环(最常用)

let arr = [10, 20, 30];
for element in arr {
    println!("{}", element);
}

// 范围
for i in 1..4 {      // 1, 2, 3
    println!("{}", i);
}

for i in (1..4).rev() {  // 3, 2, 1
    println!("{}", i);
}

循环标签

'outer: loop {
    loop {
        break 'outer;  // 跳出外层循环
    }
}

相关笔记

  • [[20260117008]] - 函数(前置:表达式概念)
  • [[20260117010]] - match 表达式(扩展:更强大的模式匹配)

来源

控制流

所有权规则

所有权是 Rust 最独特的特性,它使 Rust 无需垃圾回收即可保证内存安全。

三条核心规则

  1. Rust 中的每一个值都有一个所有者(owner)
  2. 值在任一时刻有且只有一个所有者
  3. 当所有者离开作用域,值被丢弃(drop)

作用域示例

{                      // s 无效,尚未声明
    let s = "hello";   // s 有效
    // 使用 s
}                      // 作用域结束,s 不再有效

内存管理

语言类型 内存管理方式
C/C++ 手动分配/释放
Java/Go 垃圾回收 (GC)
Rust 所有权系统(编译时检查)

Rust 的优势:

  • 无 GC 停顿
  • 无手动管理错误
  • 编译时就能发现问题

为什么需要所有权

  • 管理堆内存
  • 防止内存泄漏
  • 防止二次释放
  • 防止悬垂指针
  • 保证并发安全

相关笔记

  • [[20260117012]] - Move 语义(扩展:所有权转移)
  • [[20260117013]] - Clone 与 Copy(扩展:复制语义)
  • [[20260117014]] - 引用与借用(扩展:借用所有权)

来源

什么是所有权

Move 语义 - 所有权转移

当把堆上数据赋值给另一个变量时,Rust 默认执行**移动(move)**而非复制。

核心概念

let s1 = String::from("hello");
let s2 = s1;  // s1 的所有权移动到 s2

// println!("{}", s1);  // 错误!s1 已失效
println!("{}", s2);     // 正确

为什么这样设计

避免二次释放(double free)问题:

  • 如果 s1 和 s2 都有效,离开作用域时会释放两次同一块内存
  • Move 确保只有一个所有者,只释放一次

Move 发生的场景

  1. 变量赋值let s2 = s1;
  2. 函数参数fn take(s: String) { }
  3. 函数返回:返回值的所有权转移给调用者
fn takes_ownership(s: String) {
    println!("{}", s);
}  // s 被 drop

let s = String::from("hello");
takes_ownership(s);
// println!("{}", s);  // 错误!s 已被移动

哪些类型会 Move

  • String
  • Vec
  • Box
  • 其他堆分配类型
  • 未实现 Copy trait 的类型

相关笔记

  • [[20260117011]] - 所有权规则(前置:基本概念)
  • [[20260117013]] - Clone 与 Copy(对比:复制语义)

来源

什么是所有权

Clone 与 Copy - 复制语义

Rust 提供两种复制数据的方式:Clone(显式深拷贝)和 Copy(隐式浅拷贝)。

Clone - 深拷贝

需要显式调用,完整复制堆上的数据。

let s1 = String::from("hello");
let s2 = s1.clone();  // 深拷贝

println!("s1 = {}, s2 = {}", s1, s2);  // 两个都有效

Copy - 隐式复制

实现了 Copy trait 的类型,赋值时自动复制(栈上数据)。

let x = 5;
let y = x;  // 复制,不是移动

println!("x = {}, y = {}", x, y);  // 两个都有效

实现 Copy 的类型

类型 说明
整数类型 i32, u64 等
布尔类型 bool
浮点类型 f32, f64
字符类型 char
元组 仅当所有元素都是 Copy

Copy vs Clone

特性 Copy Clone
调用方式 隐式 显式 .clone()
数据位置 栈上 堆上
性能 快(位拷贝) 可能慢(深拷贝)
原变量 仍有效 仍有效

规则

  • 实现 Copy 的类型不能同时实现 Drop
  • 包含堆数据的类型不能实现 Copy

相关笔记

  • [[20260117011]] - 所有权规则(前置:基本概念)
  • [[20260117012]] - Move 语义(对比:移动语义)

来源

什么是所有权

引用与借用

引用允许使用值但不获取所有权。创建引用的行为称为借用(borrowing)

不可变引用

fn calculate_length(s: &String) -> usize {
    s.len()
}  // s 离开作用域,但它不拥有所有权,所以不会 drop

let s1 = String::from("hello");
let len = calculate_length(&s1);  // 借用 s1
println!("长度: {}, 内容: {}", len, s1);  // s1 仍有效

可变引用

fn change(s: &mut String) {
    s.push_str(", world");
}

let mut s = String::from("hello");
change(&mut s);

借用规则(核心)

  1. 在任意给定时刻,只能有以下之一:
    • 一个可变引用
    • 任意数量的不可变引用
  2. 引用必须总是有效(无悬垂引用)
let mut s = String::from("hello");

// 正确:多个不可变引用
let r1 = &s;
let r2 = &s;

// 错误:不能同时有可变和不可变引用
// let r3 = &mut s;  // 编译错误

// 正确:r1, r2 使用完毕后可以创建可变引用
println!("{}, {}", r1, r2);
let r3 = &mut s;  // 现在可以了

为什么这样设计

  • 防止数据竞争
  • 编译时保证并发安全
  • 无需运行时锁

相关笔记

  • [[20260117011]] - 所有权规则(前置:所有权基础)
  • [[20260117012]] - Move 语义(对比:所有权转移)

来源

引用与借用

结构体定义与实例化

结构体是自定义数据类型,用于组合多个相关的值。与元组不同,结构体的每个字段都有名称。

定义结构体

struct User {
    username: String,
    email: String,
    active: bool,
    sign_in_count: u64,
}

创建实例

let user1 = User {
    email: String::from("test@example.com"),
    username: String::from("testuser"),
    active: true,
    sign_in_count: 1,
};

// 访问字段
println!("{}", user1.email);

// 修改(需要整个实例可变)
let mut user2 = User { /* ... */ };
user2.email = String::from("new@example.com");

简写语法

// 字段初始化简写
fn build_user(email: String, username: String) -> User {
    User {
        email,      // 等价于 email: email
        username,   // 等价于 username: username
        active: true,
        sign_in_count: 1,
    }
}

// 结构体更新语法
let user2 = User {
    email: String::from("another@example.com"),
    ..user1  // 其余字段从 user1 复制
};

特殊结构体

// 元组结构体
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);  // 不同类型!

// 单元结构体(无字段)
struct AlwaysEqual;
let subject = AlwaysEqual;

相关笔记

  • [[20260117007]] - 复合数据类型(前置:元组和数组)
  • [[20260117016]] - 方法与关联函数(扩展:结构体行为)

来源

结构体的定义和实例化

方法与关联函数

方法与函数类似,但定义在结构体(或枚举、trait)的上下文中。

定义方法

使用 impl 块定义:

struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    // 方法:第一个参数是 self
    fn area(&self) -> u32 {
        self.width * self.height
    }

    // 可变方法
    fn set_width(&mut self, width: u32) {
        self.width = width;
    }

    // 获取所有权的方法
    fn consume(self) -> u32 {
        self.width * self.height
    }
}

self 参数

形式 说明
&self 不可变借用,最常用
&mut self 可变借用,可修改
self 获取所有权,调用后原值失效

关联函数

不以 self 为参数的函数,类似其他语言的静态方法。

impl Rectangle {
    // 关联函数(无 self)
    fn square(size: u32) -> Self {
        Self {
            width: size,
            height: size,
        }
    }
}

// 调用方式不同
let rect = Rectangle { width: 30, height: 50 };
let area = rect.area();           // 方法:用 .
let sq = Rectangle::square(10);   // 关联函数:用 ::

多个 impl 块

一个结构体可以有多个 impl 块:

impl Rectangle {
    fn area(&self) -> u32 { /* ... */ }
}

impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool { /* ... */ }
}

方法 vs 关联函数

特性 方法 关联函数
self 参数
调用语法 obj.method() Type::function()
类比 实例方法 静态方法/构造函数

相关笔记

  • [[20260117015]] - 结构体定义(前置:结构体基础)
  • [[20260117008]] - 函数定义(对比:普通函数)

来源

方法语法