第 1 章 函数与控制流
1.1 函数基础
rust
fn main() {
greet("小明", 25); // 调用
}
fn greet(name: &str, age: u32) { // 参数必须标注类型
println!("{} 今年 {} 岁", name, age);
}

- 关键字
fn,命名惯例是蛇形命名 (snake_case):calculate_area - 参数类型必须标注 ,返回值类型写在
->后面 - 函数可以定义在任何位置,顺序无所谓(不用先声明)
1.2 语句与表达式(重要!)
Rust 是一门基于表达式的语言,区分两个概念:
- 语句(Statement) :做事情,不返回值,以
;结尾 - 表达式(Expression) :计算并产生一个值
rust
fn main() {
let y = 6; // 这是语句,不返回值
// ❌ 错误:let 语句没有返回值,不能赋值给 x
// let x = (let y = 6);
// ✅ 代码块也是表达式,最后一个表达式(不带分号)是块的值
let x = {
let a = 3;
a + 1 // 注意:没有分号!这就是块的值
};
println!("x = {}", x); // 4
}

分号决定了表达式还是语句:
rust
fn plus_one(x: i32) -> i32 {
x + 1 // ✅ 表达式,作为返回值
// x + 1; // ❌ 加了分号变成语句,函数返回 (),类型不匹配
}

1.3 返回值
rust
// 方式一:隐式返回最后一个表达式(推荐)
fn add(a: i32, b: i32) -> i32 {
a + b
}
// 方式二:return 显式返回(用于提前返回)
fn abs(x: i32) -> i32 {
if x < 0 {
return -x; // 提前返回必须带分号
}
x
}
// 不返回值的函数,返回类型是单元类型 (),可以省略
fn say_hi() { // 等价于 fn say_hi() -> ()
println!("hi");
}
// 永不返回的发散函数:返回类型是 !
fn crash() -> ! {
panic!("程序崩溃");
}
1.4 if 表达式
rust
let number = 7;
// if 是表达式,可以赋值
let desc = if number < 10 { "个位数" } else { "多位数" };
if number % 4 == 0 {
println!("能被 4 整除");
} else if number % 3 == 0 {
println!("能被 3 整除");
} else {
println!("不能被 3 或 4 整除");
}
两个要点:
rust
// 1. 条件必须是 bool,Rust 不会自动把数字转成布尔值
// if number { } // ❌ 编译错误!不像 C/JavaScript
// 2. if 各分支的类型必须一致
// let n = if true { 5 } else { "six" }; // ❌ i32 和 &str 不匹配
1.5 三种循环
loop:无限循环
rust
let mut count = 0;
let result = loop {
count += 1;
if count == 10 {
break count * 2; // break 可以带一个值,作为整个 loop 的值!
}
};
println!("result = {}", result); // 20
while:条件循环
rust
let mut n = 3;
while n != 0 {
println!("{}!", n);
n -= 1;
}
println!("发射!");
for:遍历循环(最常用)
rust
fn main() {
let a = [10, 20, 30, 40, 50];
// 遍历数组
for element in a {
println!("值是 {}", element);
}
// 遍历区间(Range)
for i in 1..4 {
// 1, 2, 3(不含 4)
println!("{}", i);
}
for i in 1..=4 {
// 1, 2, 3, 4(含 4)
println!("{}", i);
}
for i in (1..=4).rev() {
// 反向:4, 3, 2, 1
println!("{}", i);
}
}
for比while + 索引更好:没有越界风险,而且更快(省去每次边界检查)。
循环标签:嵌套循环中精确 break/continue
rust
let mut count = 0;
'outer: loop {
let mut remaining = 10;
loop {
if remaining == 9 {
break; // 只跳出内层
}
if count == 2 {
break 'outer; // 直接跳出外层循环
}
remaining -= 1;
}
count += 1;
}
println!("count = {}", count); // 2
1.6 综合示例:摄氏华氏互转
rust
fn celsius_to_fahrenheit(c: f64) -> f64 {
c * 9.0 / 5.0 + 32.0
}
fn fahrenheit_to_celsius(f: f64) -> f64 {
(f - 32.0) * 5.0 / 9.0
}
fn main() {
for c in 0..=100 {
if c % 20 == 0 {
let f = celsius_to_fahrenheit(c as f64);
println!("{}°C = {:.1}°F", c, f); // {:.1} 保留 1 位小数
}
}
println!("98.6°F = {:.2}°C", fahrenheit_to_celsius(98.6));
}

小结
- 函数参数必须标注类型;蛇形命名;
->声明返回类型 - Rust 是表达式语言:代码块、
if、loop都能产生值,末尾不带分号的表达式就是值 loop(无限,可 break 带值)、while(条件)、for(遍历,首选)- 嵌套循环用
'标签精确控制跳出层级
第 2 章 所有权(Rust 最核心的概念)
如果说 Rust 只教会你一件事,那就是所有权。它是 Rust 无需垃圾回收却能保证内存安全的根基。这一章请慢读。
2.1 先补背景:栈与堆
程序运行时的内存主要分两块:
| 栈(Stack) | 堆(Heap) | |
|---|---|---|
| 存什么 | 大小固定、编译期已知的数据 | 大小可变/编译期未知的数据 |
| 特点 | 后进先出,分配极快 | 需向操作系统申请,较慢 |
| 例子 | i32、定长数组、元组 |
String、Vec 的数据部分 |
其他语言怎么管理堆内存?
- C/C++ :程序员手动
malloc/free、new/delete→ 容易泄漏或释放两次 - Java/Go/Python:垃圾回收器(GC)定期扫描 → 有运行时开销和停顿
Rust 走第三条路:编译器在编译期根据所有权规则,自动插入释放代码。 零运行时开销,又不会出错。
2.2 所有权三条规则
- Rust 中每一个值 都有一个所有者(owner)。
- 同一时刻,一个值只能有一个所有者。
- 当所有者离开作用域 ,值会被自动丢弃(drop)。
2.3 作用域与 String
rust
{ // s 还没出生
let s = String::from("hello"); // s 出生,是这段字符串的所有者
println!("{}", s);
} // s 离开作用域 → Rust 自动调用 drop 释放内存
// println!("{}", s); // ❌ 编译错误:s 已经不存在
String 的数据存在堆上,s 本身是栈上的一个"指针 + 长度 + 容量"结构。s 死了,堆上的数据跟着被释放------这就是规则 3。
2.4 移动(Move):Rust 最反直觉的地方
看两个对比实验:
rust
// 实验一:整数
fn main() {
let x = 5;
let y = x;
println!("x={}, y={}", x, y);// ✅ 正常,x 和 y 都能用
}

rust
// 实验二:字符串
fn main(){
let s1 = String::from("hello");
let s2 = s1;
println!("{}", s1); // ❌ 编译错误:value borrowed here after move
println!("{}", s2); // ✅ 只有 s2 能用
}

为什么? 如果 let s2 = s1 是简单复制指针,那么 s1 和 s2 都指向同一块堆内存,两人离开作用域时会释放两次(double free)------这是 C++ 的经典灾难。
Rust 的解法:let s2 = s1 时,所有权从 s1 转移给 s2,s1 立即作废。同一时刻只有一个所有者,只会释放一次。

直觉记忆法 :
=对堆数据是"过户",不是"复印"。过户完原主就没钥匙了。
而 i32 这类简单类型实现了 Copy trait,赋值时做的是真正的拷贝,所以 x 依然有效。
2.5 Clone:真的想要一份拷贝
rust
let s1 = String::from("hello");
let s2 = s1.clone(); // 深拷贝堆上的数据,两份完全独立
println!("s1={}, s2={}", s1, s2); // ✅ 都能用

clone() 有性能成本(要复制堆数据),Rust 让你显式写出来,绝不偷偷复制。
Copy vs Clone:
Copy:廉价的栈上复制(i32、f64、bool、char、全 Copy 类型的元组/数组),隐式发生Clone:可能昂贵的深拷贝,必须显式调用.clone()- 一个类型实现了
Copy就不存在"移动",赋值永远是复制
2.6 函数与所有权
传参会转移所有权
rust
fn main() {
let s = String::from("hello");
takes_ownership(s); // s 的所有权"过户"给函数
// println!("{}", s); // ❌ s 已失效
let x = 5;
makes_copy(x); // i32 是 Copy
println!("x 还能用:{}", x); // ✅
}
fn takes_ownership(some_string: String) {
println!("{}", some_string);
} // some_string 离开作用域,堆内存被释放
fn makes_copy(some_integer: i32) {
println!("{}", some_integer);
}

返回值也会转移所有权
rust
fn main() {
let s1 = gives_ownership(); // 函数把所有权"过户"出来
let s2 = String::from("hi");
let s3 = takes_and_gives_back(s2); // 过户进去,又过户出来
println!("s1={}, s3={}", s1, s3);
}
fn gives_ownership() -> String {
String::from("来自函数")
}
fn takes_and_gives_back(a_string: String) -> String {
a_string // 原样返回
}

"给出去又拿回来"的窘境
rust
fn calculate_length(s: String) -> (String, usize) {
let length = s.len();
(s, length) // 不得不把 s 一起还回来,调用者才能继续用
}
fn main() {
let s1 = String::from("hello");
let (s2, len) = calculate_length(s1); // 用元组绕一圈
println!("'{}' 的长度是 {}", s2, len);
}
这种"为了用一下值就得过户再归还"的模式太笨拙了------下一章的引用就是为此而生的 。

2.7 为什么所有权设计是天才之举
编译器在所有者离开作用域时精确插入一次 drop:
- 不会忘记释放(内存泄漏 ❌)
- 不会释放两次(double free ❌)
- 不会用到已释放的内存(use after free ❌)
- 这一切都发生在编译期,运行时零成本
代价是:你要习惯"值只有一个主人"的思维。这正是 Rust 学习曲线陡的原因,也是它可靠的来源。
小结
- 三规则:每值一主、同时唯一、出域即弃
- 堆数据赋值 = 移动(所有权过户),原变量作废
Copy类型(简单栈上类型)赋值 = 复制,原变量仍可用.clone()显式深拷贝- 函数传参/返回值都会转移所有权
