函数与流程控制
函数与表达式导向语法、三大流程控制与
match模式匹配入门。
函数:签名、尾表达式与发散
rust
fn main() {
println!("{}", add(1, 2));
println!("{}", max_of(3, 9));
println!("{}", apply(double, 21));
println!("{}", sign(-3));
}
fn add(a: i32, b: i32) -> i32 {
a + b // 最后一行没有分号:它就是返回值
}
fn max_of(a: i32, b: i32) -> i32 {
if a > b { a } else { b } // if 是表达式,直接当返回值
}
fn double(v: i32) -> i32 {
v * 2
}
/// 函数作为值:`fn(i32) -> i32` 是函数指针类型,可以当参数传
fn apply(f: fn(i32) -> i32, v: i32) -> i32 {
f(v)
}
fn sign(v: i32) -> i32 {
if v < 0 {
return -1; // 提前返回:必须显式写 return
}
if v == 0 {
return 0;
}
1 // 兜底值
}参数必须标类型
rust
// fn add(a, b) { a + b } // error: expected one of `:`, `@`, or `|`, found `,`💡 对照:Python 用类型注解但运行期才用得上;JS 完全无类型;Java/Go 也要求写类型。Rust 的要求不是「啰嗦」,而是与类型推断的边界有关:函数是模块的公开接口,签名必须自洽可读,编译器不允许它依赖调用点来推断,否则库的接口会随调用方式而变。
分号陷阱:最后一个表达式
rust
fn sum_to(n: i32) -> i32 {
let mut acc = 0;
for i in 1..=n {
acc += i;
}
acc // ← 有分号就变成语句,函数返回 ()
}去掉 acc 的分号是正确写法。加上的话报错:
error[E0308]: mismatched types
--> src\main.rs:1:22
|
1 | fn sum_to(n: i32) -> i32 {
| ------ ^^^ expected `i32`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression
...
6 | acc;
| - help: remove this semicolon to return this value🧠 原理:
{ ... }的值是块尾部表达式的值。acc;是语句(statement),语句的值是(),所以块的值变成()。这就是「Rust 里分号有语义」的原因,也是expected (), found i32这类报错最常见的来源。
发散函数 !(never type)
-> ! 表示「这个函数永不返回」。panic!、todo!、unreachable!、std::process::exit 都是发散表达式。
rust
#[derive(Debug)]
enum Coin { Penny, Nickel }
fn value(c: Coin) -> i32 {
match c {
Coin::Penny => 1,
Coin::Nickel => panic!("还没实现"), // panic! 是 !,可以当 i32 用
}
}
fn parse_level(s: &str) -> u8 {
let Ok(v) = s.parse::<u8>() else {
todo!("待实现的输入:{s}"); // todo! 也是 !
};
v
}
fn main() {
println!("{}", value(Coin::Penny));
if std::env::args().count() > 99 {
println!("{}", parse_level("3"));
}
}🧠 原理:
!是「底类型(bottom type)」,它能强制转换(coerce)到任何类型——因为既然永不返回值,类型检查器就不必关心它「本该」是什么类型。这让panic!可以直接放在match的分支、if的某一臂、let的初始化位置,而不会引起类型不匹配。
| 宏/函数 | 含义 | 与 ! 的关系 |
|---|---|---|
panic!("msg") | 立即终止当前线程并打印信息 | 返回 ! |
todo!() / unimplemented!() | 占位:编译通过、运行到这里 panic | 返回 ! |
unreachable!() | 断言「逻辑上到不了这里」 | 返回 ! |
assert!(cond) / assert_eq!(a, b) | 条件不成立则 panic | 返回 (),但分支内是 ! |
std::process::exit(1) | 立刻结束进程 | 返回 ! |
⚠️ 陷阱:
todo!()是运行期 panic,不是编译错误。把todo!()留在发布代码里,就等于在生产环境埋了个定时炸弹。想让它变成硬性检查,clippy::todolint 可以帮忙。
语句 vs 表达式:Rust 最关键的一条语法差异
Rust 里几乎一切都是表达式:
| 语法结构 | 是表达式吗 | 它的值 |
|---|---|---|
块 { ... } | 是 | 尾部表达式的值(没有尾部表达式则为 ()) |
if cond { a } else { b } | 是 | 被选中分支的值(两分支类型必须一致) |
match x { ... } | 是 | 被选中分支的值 |
loop { ... } | 是 | break value 的值 |
while / for | 否(值为 ()) | 不能出现在需要值的位置 |
let 绑定 | 否(是语句) | 无值,返回 () |
赋值 x = 1 | 是 | () |
函数调用 f() | 是 | 返回类型 |
rust
fn main() {
// 块作为表达式
let area = {
let w = 3;
let h = 4;
w * h // 块的「尾部表达式」就是块的值
};
// if 作为表达式(替代三目运算符)
let n = 7;
let parity = if n % 2 == 0 { "even" } else { "odd" };
// match 作为表达式
let word = match n {
0 => "zero",
1..=5 => "small",
_ => "big",
};
println!("{area} {parity} {word}");
// 赋值表达式的值是 ():所以不能写 a = b = 1
let mut a = 0;
a = 1;
// let b = (a = 2); // error[E0308]: mismatched types:期望 i32,实际是 ()
}输出:
12 odd big。
🧠 原理:为什么 Rust 坚持「赋值返回
()」?因为如果a = b有值,就必然要定义「赋值的值是什么」(是被赋的值?还是旧值?),而 C 家族因此产生了if (x = 0)这类真实 bug。Rust 用()斩断了这种可能性,同时这也解释了为什么while (line = read())在 Rust 里写不出来——必须用while let Some(line) = read()或loop { let line = ...; }。
⚠️ 陷阱:
if表达式若用作值,必须有else分支,否则类型是();if a { 1 }(无else)不能赋给i32。
流程控制
if / else if / else
rust
fn main() {
let score = 85;
let grade = if score >= 90 {
'A'
} else if score >= 80 {
'B'
} else if score >= 60 {
'C'
} else {
'F'
};
println!("{grade}"); // B
}⚠️ 陷阱:条件必须是
bool,没有 truthy/falsy。JS 里if ([])、if ("0")均为真;Python 里if []为假。Rust 里if count { ... }编译失败,if count = 1也编译失败:
error[E0308]: mismatched types
--> src\main.rs:3:8
|
3 | if count = 1 {
| ^^^^^^^^^ expected `bool`, found `()`
|
help: you might have meant to compare for equality
|
3 | if count == 1 {
| +loop、while、while let
rust
fn main() {
// loop:无限循环,可以用 break 带出值
let mut n = 0;
let total = loop {
n += 1;
if n == 4 {
break n * 10; // 循环的「返回值」
}
};
println!("{total}"); // 40
// while:条件为 bool
let mut k = 3;
while k > 0 {
k -= 1;
}
println!("{k}"); // 0
// while let:「每次迭代解构一个可能失败的模式」
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
print!("{top} "); // 3 2 1
}
println!();
// loop + break 用于「没有自然条件」的循环:比 while true + 哨兵变量更清晰
let mut guess = 0;
let found = loop {
guess += 1;
if guess * guess >= 20 {
break guess;
}
};
println!("{found}"); // 5
}💡 对照:Java 用
do { } while (cond),Rust 没有do-while;惯用替代是loop { ...; if !cond { break; } }。Python 的while ... else、JS 的do...while都没有对应物,因为break value和标签已经覆盖了绝大多数「循环带出结果」的需求。
for ... in 与三种迭代形式
for 的底层是 IntoIterator trait(〈并发编程〉一章展开)。现在只需要会区分这三种写法:
| 写法 | 迭代产生 | 元素类型 | 循环体能否修改元素 | 循环后原集合能否使用 | 类比 |
|---|---|---|---|---|---|
for v in &xs | 共享借用 | &T | 否(只能读) | 能 | Python 的 for v in xs(只读语义) |
for v in &mut xs | 可变借用 | &mut T | 能(*v = ...) | 能 | 无直接对应 |
for v in xs(或 xs.into_iter()) | 取得所有权 | T | 能(拿到的是值) | 不能(被移动了) | Python 的 for v in xs(但 Rust 会移动) |
for i in 0..n | 半开区间 | i32/推断整型 | 不适用 | — | range(n) |
for i in 0..=n | 闭区间,含 n | 整数 | 不适用 | — | range(n + 1) |
rust
fn main() {
let mut nums = vec![1, 2, 3];
for v in &nums {
print!("{v}"); // 1 2 3:只读借用
}
println!();
for v in &mut nums {
*v *= 2; // 通过可变借用改元素
}
println!("{nums:?}"); // [2, 4, 6]
for v in nums { // 取得所有权:nums 之后不能再用
print!("{v}"); // 2 4 6
}
println!();
for i in 0..3 {
print!("{i}"); // 0 1 2
}
println!();
for i in 0..=3 {
print!("{i}"); // 0 1 2 3
}
println!();
// 显式方法形式:.iter() / .iter_mut() / .into_iter()
let vals = vec![10, 20];
for v in vals.iter() {
print!("{v} "); // 10 20
}
println!();
for v in vals.into_iter() {
print!("{v} "); // 10 20
}
println!();
}🧠 原理:
for v in &xs是for v in (&xs).into_iter()的语法糖;&Vec<T>实现了IntoIterator<Item = &T>,&mut Vec<T>实现IntoIterator<Item = &mut T>,Vec<T>自己实现IntoIterator<Item = T>。同一个for关键字,元素类型由你给的集合形式决定——这就是为什么 Rust 的for不需要.items()/.values()之类的辅助方法,也不会像 JS 的for...in那样给出字符串键。
⚠️ 陷阱:
for v in xs之后再用xs会得到error[E0382]: use of moved value: xs。新手最常见的一类报错就来自这里——解决办法是按需改成&xs或.iter()。
break / continue 与标签
rust
fn main() {
let grid = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
'rows: for (r, row) in grid.iter().enumerate() {
for (c, &v) in row.iter().enumerate() {
if v == 5 {
println!("found 5 at ({r}, {c})");
break 'rows; // 直接跳出外层循环
}
if v % 2 == 0 {
continue; // 只跳过内层本次迭代
}
print!("{v} ");
}
}
println!();
}输出:
1 3 found 5 at (1, 1)——内层continue只跳过当前格,break 'rows直接结束两层循环,所以7 9没有被打出来。
- 标签(label)写作
'名字:,作用于loop/while/for,可被break 'name(可带值)和continue 'name使用。 - 循环也是表达式:
loop { break 1; }的类型是i32;while/for的值是(),所以不能let x = for ... {}。
模式匹配:match、if let、let else
match 是 Rust 的瑞士军刀:它同时做分支和解构,并且必须穷尽。
穷尽性、_ 与绑定
rust
fn main() {
let n = 3;
let word = match n {
0 => "zero",
1..=5 => "small", // 范围模式:闭区间
6 | 7 | 8 => "mid", // 或模式
_ => "big", // 通配:必须兜底
};
println!("{word}"); // small
// 绑定:把整个值绑到名字上
let x = 42;
match x {
hit @ 40..=49 => println!("hit {hit}"),
other => println!("other {other}"),
}
// 守卫(guard):在模式之后追加 if 条件
let pair = (3, 7);
match pair {
(a, b) if a == b => println!("diagonal {a}"),
(a @ 1..=3, b) => println!("low {a} {b}"),
_ => println!("other"),
}
}输出:
small、hit 42、low 3 7。
🧠 原理:
match的穷尽性检查是编译期的。编译器会用穷尽性算法(exhaustiveness checking)验证所有可能值都被覆盖,覆盖不到就报error[E0004]: non-exhaustive patterns。这就是为什么「忘写_」只会在编译期炸,而不会像 C 的switch那样悄悄跳过——C 家族里忘记default的代价是运行期拿到未初始化的垃圾值。
⚠️ 陷阱:绑定会把所有值都匹配掉,所以
other => ...之后的臂是死代码。同理,n => ...写在前面的臂会让后面的臂永远不可达,编译器一般会给出unreachable pattern警告。
解构元组、数组、结构体、枚举
rust
#[derive(Debug)]
struct Point { x: i32, y: i32 }
fn main() {
// 元组
let (a, b) = (1, 'x');
println!("{a} {b}");
// 数组/切片模式
match &[1, 2, 3][..] {
[] => println!("empty"),
[only] => println!("one: {only}"),
[first, .., last] => println!("{first} .. {last}"), // 1 .. 3
}
// 结构体:按字段名解构(不需要顺序一致)
let p = Point { x: 1, y: 2 };
let Point { x, y } = p;
println!("{x} {y}");
let Point { x: px, .. } = p; // 只取 x,其余用 .. 忽略
println!("{px}");
// 枚举 + Option/Result(完整讲解见〈集合与迭代器〉)
let maybe: Option<i32> = Some(5);
match maybe {
Some(v) if v > 3 => println!("big {v}"),
Some(v) => println!("small {v}"),
None => println!("none"),
}
let parsed: Result<i32, _> = "7".parse();
match parsed {
Ok(v) => println!("ok {v}"),
Err(e) => println!("err {e}"),
}
// matches! 宏:只想知道「是否匹配」,不需要解构
println!("{}", matches!(maybe, Some(v) if v > 3)); // true
println!("{}", matches!('q', 'a'..='m' | 'A'..='M')); // false
}输出:
1 x、1 .. 3、1 2、1、big 5、ok 7、true、false。
💡 对照:Java 的
switch只能匹配常量(现代 Java 支持模式匹配,但必须写default才不会漏);Python 的match是 3.10+ 且不检查穷尽性;Go 的switch默认不贯穿。Rust 的match三者兼有:能解构、能守卫、且强制穷尽。
if let、if let ... else、let else(1.65+)
rust
fn main() {
let maybe: Option<i32> = Some(1);
// if let:只关心一个模式时,比 match 少一层缩进
if let Some(v) = maybe {
println!("got {v}");
} else {
println!("nothing");
}
// 多条件串联(if let 链):Rust 2024 起可直接用 && 连接(1.88.0 稳定)
let config: Option<(u8, &str)> = Some((3, "debug"));
if let Some((level, _)) = config
&& level >= 3
{
println!("verbose");
}
// let else:`let PATTERN = expr else { ... }`,失败分支必须发散(return/break/panic!)
let text = "2024";
let Ok(year) = text.parse::<u32>() else {
println!("解析失败");
return;
};
println!("year = {year}");
// 用 let else 做「早退卫语句」,避免层层嵌套
let first = [3, 1, 2].first().copied();
let Some(head) = first else {
println!("空数组");
return;
};
println!("head = {head}");
}输出:
got 1、verbose、year = 2024、head = 3。
⚠️ 陷阱:
let else右侧的模式必须是可反驳(refutable)的,并且else块必须发散(类型为!)。写let Some(x) = maybe else { 0 };会报类型错误,因为0不是!。另外let Ok(v) = ...里的绑定在else块中不可见。
注释与文档注释
rust
//! 模块级文档注释(inner doc):写在文件/模块顶部,描述这个模块本身。
//! 只能出现在模块开头——所以它必须放在所有 item(包括 `use`)之前。
// 单行注释:解释「为什么」,不是「做了什么」
/* 块注释:可以 /* 嵌套 */,因此可以临时注释掉整段带注释的代码 */
/// 项目级文档注释(outer doc):给紧跟其后的项写文档,支持 Markdown。
///
/// # Examples
///
/// ```
/// let r = my_crate::double(21);
/// assert_eq!(r, 42);
/// ```
fn double(v: i32) -> i32 {
v * 2
}
#[doc = "属性形式的文档注释,等价于 `///`,常用于宏生成的代码"]
pub const LIMIT: u32 = 100;
fn main() {
println!("{}", double(21));
// SAFETY: 无 unsafe 操作,这里只是演示注释约定(见下)
let _ = LIMIT;
}///:为紧随其后的项(函数、结构体、模块声明)写文档,cargo doc会渲染成 HTML。//!:为包含它的模块/crate 写文档,必须位于文件顶部(//!是 inner attribute,///是 outer)。#[doc = "..."]:属性形式,等价于///;宏生成文档时只能用它。// SAFETY:约定:每个unsafe块之前都应写清「为什么这里满足安全前提」。这是社区标准(clippy::undocumented_unsafe_blocks会检查),因为unsafe的正确性依赖人的推理,代码本身无法表达。
🚀 进阶:文档里的代码块会被
cargo test当作 doctest 编译并运行,所以写文档示例时它会自动帮你验证代码不会过时。想深入打磨 API 文档,可查阅 Rustdoc 官方手册(入口见附录 D)。
与其他语言的对照
| 主题 | Python | Java | JS/TS | C/C++ | Go | Rust 的做法与原因 |
|---|---|---|---|---|---|---|
| 变量可变性 | 全部可变 | final 才不可变 | const/let 只是重绑定限制 | 默认可变 | 默认可变 | 默认不可变,要写 let mut;把「会不会被改」写进类型检查 |
| 常量 | 无真正的常量 | static final | const(对象内容仍可变) | #define/const | const/var | const 编译期内联,static 有地址;static mut 需 unsafe |
| 条件表达式 | a if c else b | c ? a : b | c ? a : b | c ? a : b | 无三目 | 直接用 if c { a } else { b },因为 if 本身就是表达式 |
| 多分支 | if/elif、3.10+ match | switch(易忘 default) | switch(=== 陷阱)/if | switch(默认贯穿) | switch(不贯穿) | match 强制穷尽、可解构、可守卫;漏分支是编译错误 |
for 形式 | for x in it | for (T x : c) | for...of/for...in | for (i=0;…) | for i, v := range | for v in xs/&xs/&mut xs/0..n,元素类型由迭代形式决定 |
| 自增运算符 | 无 ++ | i++ 有值 | i++ 有值 | i++ 有值 | i++ 是语句 | 没有 ++/--;用 += 1。因为赋值返回 (),无法定义「后缀返回旧值」 |
| 整数溢出 | 任意精度,不溢出 | 静默环绕 | 双精度浮点,超 2⁵³ 丢精度 | 有符号溢出是 UB | 静默环绕 | debug panic、release 环绕(有定义),并提供 checked_*/wrapping_* 等显式 API |
| 数值隐式转换 | 自动提升 | 自动提升 | 弱类型自动转 | 整型提升、隐式窄化 | 需显式转换 | 完全禁止隐式转换;as 是显式且可能丢数据,From/TryFrom 表达意图 |
| 字符串可变性 | str 不可变 | String 不可变 | 不可变(需新建) | char* 可改 | string 不可变 | String(可增长)与 &str(借用视图)分离,UTF-8 保证由类型系统维护 |
null | None | null(可赋给任何引用) | null/undefined | NULL/nullptr | nil | 没有 null;用 Option<T> 强制处理「可能没有」的情况 |
| 数组与列表 | list 动态、tuple 定长 | 数组定长、ArrayList 动态 | Array 动态 | 数组退化指针、std::vector | 数组定长、切片动态 | 数组 [T; N] 长度进类型、&[T] 借用视图、Vec<T> 拥有堆数据 |
| 错误处理 | 异常 | 受检/非受检异常 | 异常 + Promise reject | 错误码/异常 | 多返回值 error | Result<T, E> 是普通值;? 传播(〈错误处理〉一章) |
| 函数返回值 | return/尾表达式 | 必须 return | 必须 return | 必须 return | 必须 return | 尾部表达式即返回值,return 只用于提前退出 |
| 未使用变量 | 无警告 | 无警告 | 无警告 | 警告 | 编译错误 | 警告(unused_variables),_ 前缀显式表示「故意不用」 |
常见坑与编译错误
坑 1:cannot find value / cannot find function
error[E0425]: cannot find value `y` in this scope
--> src\main.rs:2:13
|
2 | let x = y + 1;
| ^ not found in this scope原因:名字拼错、变量还没定义、或者忘了 use(例如 HashMap 需要 use std::collections::HashMap;)。 修法:检查拼写与定义顺序;需要导入时按编译器提示补 use;作用域不同时把定义提到使用之前。
坑 2:忘记分号 → expected (), found i32
error[E0308]: mismatched types
--> src\main.rs:1:22
|
1 | fn sum_to(n: i32) -> i32 {
| ------ ^^^ expected `i32`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression
...
6 | acc;
| - help: remove this semicolon to return this value原因:函数体最后一个表达式后面多了 ;,块的值就变成 ()。 修法:删掉那个分号,或用 return acc;。块内同理(let total = { ...; acc };)。
坑 3:if 两分支类型不一致
error[E0308]: `if` and `else` have incompatible types
--> src\main.rs:3:34
|
3 | let n = if flag { 1 } else { "zero" };
| - ^^^^^^ expected integer, found `&str`原因:if 当表达式用时,两分支必须是同一类型(if 没有公共父类型可退,enum 才是 Rust 表达「二者之一」的方式)。 修法:统一类型,或把结果包成 enum/Option/Result,或改成「两个分支各自 println!」而不是取值。
坑 4:match 非穷尽
error[E0004]: non-exhaustive patterns: `i32::MIN..=0_i32` and `3_i32..=i32::MAX` not covered
--> src\main.rs:3:22
|
3 | let word = match n {
| ^ patterns `i32::MIN..=0_i32` and `3_i32..=i32::MAX` not covered
|
= note: the matched value is of type `i32`原因:漏了分支。注意编译器会精确告诉你是哪些区间没覆盖。 修法:补上剩余分支,或加 _ => ... 兜底。若用 _,建议在注释里写明「这里有意忽略 X」,否则以后枚举加变体会静默走兜底分支。
坑 5:把 String 和 &str 混用
error[E0308]: mismatched types
--> src\main.rs:3:11
|
3 | greet(name);
| ----- ^^^^ expected `&str`, found `String`
| |
| arguments to this function are incorrect
|
help: consider borrowing here
|
3 | greet(&name);
| +原因:String 不自动变成 &str 作为函数实参(&String 会在需要 &str 时经 Deref 自动转换,但 String 本身不会)。 修法:传 &name(顺带避免移动);函数参数尽量写成 &str,这样数组、字面量、String 借用都能传。
坑 6:as 静默截断
rust
fn main() {
let big = 300i32;
let small = big as u8; // 不报错、不 panic,结果是 44
println!("{small}");
}原因:as 的语义就是「按位窄化」。编译器默认不警告(clippy::cast_possible_truncation 可以开启检查)。 修法:用 u8::try_from(big) / big.try_into() 明确处理失败,或 checked_* 系列,并注释说明为何断言截断是安全的。
坑 7:unused variable / 未使用的 mut
warning: unused variable: `unused`
--> src\main.rs:2:9
|
2 | let unused = 10;
| ^^^^^^ help: if this is intentional, prefix it with an underscore: `_unused`warning: variable does not need to be mutable
--> src\main.rs:2:9
|
2 | let mut x = 1;
| ----^
| |
| help: remove this `mut`原因:变量从未被读 / 从未被写。 修法:能删就删;确实需要占位时用 _name(保留绑定,仍会析构)或 _(不绑定)。别用 #[allow(unused_variables)] 一把梭——它会掩盖真正的遗漏。
坑 8:移动后再使用(借用检查器直觉)
error[E0382]: borrow of moved value: `text`
--> src\main.rs:12:21
|
10 | let text = String::from("hi");
| ---- move occurs because `text` has type `String`, which does not implement the `Copy` trait
11 | let t2: String = text;
| ---- value moved here
12 | println!("{t2} {text}");
| ^^^^ value borrowed here after move原因:String 没有实现 Copy,赋值/传参是移动而非复制;移动后原绑定失效。i32、bool、char、&T 这类实现了 Copy 的类型不受影响。 修法:需要两份数据就 .clone()(要付出分配成本);只想借用就传 &text;把所有权给了函数又想要回来,就让函数返回它(〈所有权〉一章会给出更好的方案)。
🧠 原理:移动检查发生在编译期,基于每个绑定的「有效/失效」状态做数据流分析。它的目标是:任何时刻对某个值最多只有一个所有者,从而释放内存时不会出现双重释放(double free)或用后释放(use after free)。
速查表
| 想做的事 | 写法 |
|---|---|
| 不可变绑定 / 可变绑定 | let x = 1; / let mut x = 1; |
| 改变量类型或改名 | 遮蔽:let x = x as f64; |
| 编译期常量 / 全局 | const N: usize = 4; / static NAME: &str = "x"; |
| 整数类型选择 | 默认 i32;索引/长度用 usize;位运算用 u8/u32;大计数用 i64/u64 |
| 安全算术 | a.checked_add(b)→Option、a.wrapping_add(b)、a.saturating_add(b)、a.overflowing_add(b) |
| 类型转换 | 拓宽 i64::from(x)/x.into();可能失败 u8::try_from(x)/x.try_into();位级 x as u8 |
| 解析文本 | "42".parse::<i32>()?(或 .expect("...")) |
| 元组解构 / 取字段 | let (a, b) = t; / t.0 |
| 数组 / 切片 | [T; N] / &arr[1..]、&s[..2] |
| 字符串字面量 → 拥有 | "x".to_string()、String::from("x")、format!("{a}{b}") |
| 提前返回 / 发散 | return v; / panic!、todo!、unreachable!(类型 !) |
块/if/match 取值 | 尾部不加分号:let v = { f(); 42 }; |
条件(必须 bool) | if x > 0 { } / if let Some(v) = o { } |
| 循环取结果 | let v = loop { break 42; }; |
| 跳出外层循环 | 'outer: for ... { break 'outer; } |
| 迭代三种方式 | for v in &xs / for v in &mut xs / for v in xs |
| 穷尽分支 | match v { ... },必要时 _ => ... |
| 只匹配一个模式 | if let Some(v) = o { } else { };早退用 let ... else { return; } |
| 只判断是否匹配 | matches!(v, Pattern) |
| 忽略未使用值 | let _ = f();(不绑定)/ let _x = f();(绑定但静默) |