Rust 专题【左扬精讲】—— 选择控制语句、运算符与格式化输出

Rust 专题【左扬精讲】—— 选择控制语句、运算符与格式化输出

Rust 控制流 运算符 格式化输出 Pattern Matching 多语言对比

学习重点提示建议先通读全文,再重点回顾标注内容

重点掌握(必须)

  • match 模式匹配:Rust 最强大的控制流工具,比 C 族 switch 更强大,可解构、范围匹配、卫表达式
  • if 是表达式:Rust 的 if/else 返回值,所有分支必须返回同类型值
  • 循环返回值loop 可以有返回值(break value),for 遍历迭代器
  • println! 格式化:占位符 {}(Display)、:?}(Debug)、{name}(命名参数)

次重点(理解即可)

  • 运算符优先级与 C 族的差异
  • 类型转换 asFrom/Into trait 的对比
  • Rust 与 Python/JS 在格式化输出上的差异

目录


FAQ · 临考前速背(20 组)

思考记忆提示FAQ 是全篇的"临考前速背"模块,建议在通读全文后作为复习使用

  • Q1-Q7 围绕选择控制语句:if/else、match、loop/for/while 的特性
  • Q8-Q14 围绕运算符:整数溢出、逻辑运算、类型转换、等价性比较
  • Q15-Q20 围绕格式化输出与多语言对比:println! vs fmt vs print 等

Q1. Rust 的 if/else 和 C 族语言的 if 有什么本质区别?

Rust 的 if 是表达式(Expression),可以返回值;C 族的 if 是语句(Statement),没有返回值。在 Rust 中,let x = if condition { 1 } else { 2 }; 是合法代码,x 的值是 1 或 2。这要求 if 的所有分支返回相同类型。在 C 中,if 只能控制执行流程,不能作为右值。

Q2. Rust 的 match 比 C 的 switch 强大在哪里?

match 支持解构(Destructure)、范围匹配(1..=5)、卫表达式(Guard)、通配符(_),且每个分支的值是表达式的结果,可以返回值;switch 仅支持整数/枚举常量比较。例如:match x { Some(n) if n > 0 => ... } 中卫表达式(if n > 0)允许在模式匹配后再加条件过滤,这是 switch 完全做不到的。

Q3. Rust 为什么没有三元运算符(?:)?

因为 Rust 的 if 是表达式,可以直接替代三元运算符的功能,不需要额外的语法。在 C 中需要 int x = a > b ? a : b;,在 Rust 中直接写 let x = if a > b { a } else { b };,语义更清晰,分支可以是多行代码块。三元运算符本质上是"表达式版本的 if",Rust 已经有了更好的方案。

Q4. Rust 的 loopwhile / for 有什么区别?

loop 是无限循环,需要手动用 break 退出;while 是条件循环;for 是迭代器循环。关键区别是 loop 可以通过 break value 返回值,而 while/for 不行。loop 是 Rust 最接近"do-while"的概念(至少执行一次),配合标签('label:)可以从深层嵌套循环中直接 break。

Q5. Rust 的 for 循环和 C 的 for 循环有什么不同?

Rust 的 for 只能遍历迭代器(Iterator),没有 C 式的 for (i=0; i<n; i++) 三段式语法;Rust 通过 for item in collectionfor i in 0..n 实现等效功能。这是 Rust 推崇的"迭代器即循环"哲学——迭代器在 Rust 中无处不在(VecHashMapRange 都是迭代器),链式调用 .map().filter().collect() 比三段式 for 循环更表达力强。

Q6. Rust 的 match 必须穷举所有可能吗?

是的。Rust 的 match 强制穷举(Exhaustive Matching),编译器会检查是否所有可能都被覆盖;未覆盖的变体会报错,除非使用通配符 _.. 来捕获其余情况。这个特性保证了每次 match 都是完整的,不会出现 C switch 忘记写 default 导致的安全隐患。Rust 编译器还会提示"缺少哪些变体",是极佳的编码辅助。

Q7. Rust 支持 return 从循环中返回值吗?

支持,但更 Rust 风格的做法是用 break valueloop 表达式返回值——return 用于从函数中提前返回,break 用于从循环中跳出。break 返回值让 Rust 的循环成为"表达式",可以赋值给变量:let result = loop { break 42; };(result = 42)。这是 Rust 推崇的"用表达式而非语句"的函数式风格。

Q8. Rust 的整数溢出处理和 C/C++ 有什么不同?

Rust 在 Debug 模式下会 panic(在溢出时崩溃),在 Release 模式下使用二进制回绕(wrapping);C/C++ 是未定义行为(UB),编译器可以自由优化,可能产生意想不到的结果。Rust 提供了 .wrapping_add().checked_add().saturating_add().overflowing_add() 四个方法让开发者显式选择溢出处理策略,安全性远高于 C/C++ 的 UB。

Q9. Rust 的逻辑运算符(&& / ||)和 C 族有什么不同?

行为相同(短路求值),但返回类型不同——Rust 逻辑运算符返回的是布尔值 bool,不是整数(0/1),且 Rust 不允许将布尔值隐式转换为整数。在 C 中,a && b 返回的是 0 或 1(整数),可以用作整数参与算术运算;Rust 的布尔运算是纯布尔代数,不允许隐式混入算术运算,更安全。

Q10. Rust 的位运算符和 C 完全一样吗?

大部分相同(&|^<<>>),但有一个重要区别:Rust 的移位运算符要求右操作数的类型必须是 usize,且移位量必须小于左操作数的位数。在 C 中,移位量超过类型位数是未定义行为(UB);Rust 会在编译期或运行时检查移位量合法性,超出时 panic(Debug)或回绕(Release),不会产生 UB。

Q11. Rust 的 ===== 有什么区别?Rust 有 === 吗?

Rust 只有 ==,没有 ===。Rust 的 == 比较值的相等性(由 PartialEq trait 定义),不存在 JS 中"值相等但类型不等"的歧义。在 JS 中,== 会做类型强制转换("5" == 5 为 true),=== 不做类型转换。Rust 从设计上拒绝了隐式类型强制转换,所以只需要一个相等比较运算符,消除了 == vs === 的困惑。

Q12. Rust 的类型转换用 as 和用 From/Into trait 有什么区别?

as 是编译期已知的安全类型转换(数值类型之间、基本指针转换);From/Into 是 trait,用于实现自定义类型之间的转换,是更通用的抽象。经验法则:数值类型之间用 as(如 i32 as u64),自定义类型或语义化转换用 From/Into(如 String::from("hello"))。as 转换不调用任何运行时代码(零成本),而 From/Into 可能涉及堆分配。

Q13. Rust 支持运算符重载(Operator Overloading)吗?

支持,但只能通过 trait 实现,不能自定义运算符符号本身——可以重载哪些运算符由标准库决定。Rust 通过实现 trait 来重载运算符:Add+)、Sub-)、Index[])、Deref*)等。标准库中的 Vec<T> 实现了 Index<usize>,所以可以用 v[0] 访问元素。这与 C++ 的自由运算符重载(可以在任何地方定义 operator+)不同,Rust 限制更严格,更安全。

Q14. Rust 的运算符优先级和 C 一样吗?

大部分相同,但 Rust 的优先级表比 C 更简洁——Rust 只有 17 级优先级(不包括路径分隔符),且逻辑运算符(&&)优先级低于比较运算符,这与 C 的 && vs == 关系一致。Rust 的优先级表在官方文档中有明确定义,不像 C 中存在一些"陷阱"(如 C 中 x << y + z 容易被误解)。Rust 社区推荐用括号明确表达优先级,即使在优先级清晰的情况下——这是"显式优于隐式"哲学的体现。

Q15. Rust 的 println! 和 C 的 printf 有什么本质区别?

println! 是宏,接受的是格式化参数列表(编译期检查格式字符串的类型安全性),而 printf 是函数,使用 %d/%s 占位符在运行时解析格式(类型不安全)。Rust 的编译器会检查 println! 的参数类型是否与格式占位符匹配——如果传入的字符串给到了 {} 占位符但类型不匹配,编译器会报错(这解决了 C 中 printf("%s", integer) 不会报错但会产生垃圾输出的问题)。

Q16. Rust 的 {}(Display)和 :?}(Debug)占位符有什么区别?

{} 调用 Display trait,用于为用户设计的自定义格式化输出;{:?} 调用 Debug trait,用于调试输出,默认格式为 TypeName { field: value, ... }所有基本类型(i32String 等)同时实现了 DisplayDebug。对于自定义类型,需要用 #[derive(Debug)] 或手动实现 Debug trait;Display 必须手动实现。

Q17. Rust 有类似 Python f-string 的字符串插值吗?

Rust 没有 f-string,但有等效的宏 format!(生成字符串)和 println!(打印输出),配合 {name} 命名参数或 {0}/{1} 位置参数。Python 的 f"hello {name}" 在 Rust 中对应 format!("hello {}", name)format!("hello {name}", name = name)。Rust 的 format! 同样在编译期检查类型安全性。

Q18. Rust 的 dbg! 宏有什么用?和 println! 有什么区别?

dbg! 是 Rust 1.32 引入的调试宏,它打印调试信息(包括表达式所在的文件和行号),并返回表达式的值(可以内联使用)。let x = dbg!(a + b); 会打印 [文件名:行号] a + b = 结果,同时 x 的值就是计算结果。这比 println!("{:?}", a + b) 更方便,因为不需要写两次表达式。

Q19. Rust 的 eprintln!println! 有什么区别?

println! 输出到标准输出(stdout),eprintln! 输出到标准错误(stderr)。两者的格式化参数完全一致,区别仅在于输出流。在 Unix 系统中,stdout 和 stderr 是分开的流,可以用 command > output.txt 2> error.txt 将正常输出和错误信息分开处理。Rust 标准库还有 print!(无换行)和 eprint!(无换行 + stderr),以及 writeln!/write!(输出到 Write trait 的实现者,如文件或 Vec<u8>)。

Q20. Rust 的 match 能用在哪些地方?除了 match 表达式,还有其他用法吗?

match 是表达式(可以返回值),可以用在任何需要表达式的地方,如变量赋值、函数返回值、甚至作为宏的参数。Rust 还提供了简化写法:if let(单模式匹配)、while let(循环匹配)、let else(模式+提前返回)。if let Some(x) = option { ... } 等价于 match option { Some(x) => { ... }, None => { } },语法更简洁。let else 是 Rust 1.65 引入的语法:let Some(x) = option else { return; };,模式不匹配时执行 else 块并提前返回。

全篇必记总纲

Rust 的控制流、运算符和格式化输出都体现了"安全优先 + 显式优于隐式"的设计哲学:if/match 是表达式(返回值),for 遍历迭代器(无三段式),整数溢出有四种显式处理策略,println! 在编译期检查类型安全——相比 C 族语言,Rust 在保持语法简洁的同时消除了大量运行时隐患。


Roadmap · 学习路线图

控制流是程序的"神经网络",运算符是"逻辑细胞",格式化输出是"信息输出"——三者共同构成程序行为的基础构件。

What 是什么 if/match/loop/for/while 的语法与特性;运算符分类;格式化宏家族
Why 为什么 表达式 vs 语句的区别、match 穷举保证、println! 编译期安全检查的意义
How 怎么做到的 match 卫表达式与解构、as 转换规则、Debug/Display trait 实现、Iterator 惰性求值

一、What · 核心概念解析

1.1 if / else 表达式

在 Rust 中,if 是表达式而非语句——这意味着它可以返回值。这个设计来自函数式语言(如 ML、Haskell),让 Rust 可以写出更简洁的条件逻辑。

// Rust:if 是表达式,返回值
let score = 85;
let grade = if score >= 90 {
    "A"
} else if score >= 75 {
    "B"
} else {
    "C"
};
println!("Grade: {}", grade); // Grade: B

// 注意:所有分支必须返回相同类型
// let bad = if x > 0 { "positive" } else { 100 }; // 编译错误:类型不匹配

表达式 vs 语句

  • 表达式(Expression):求值并返回值,如 a + bif ... { } else { }
  • 语句(Statement):执行操作但不返回值,如 let x = 1;if ... { };

1.2 match 模式匹配

match 是 Rust 最强大的控制流工具,比 C 的 switch 更通用、更安全、更表达力强。每个分支可以是表达式,返回 match 的值。

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(u8, u8, u8),
}

fn process(msg: Message) -> i32 {
    match msg {
        Message::Quit => {
            println!("退出");
            0
        }
        Message::Move { x, y } => {
            println!("移动到 ({}, {})", x, y);
            x + y
        }
        Message::Write(text) => {
            println!("写: {}", text);
            text.len() as i32
        }
        Message::ChangeColor(r, g, b) => {
            (r as i32) + (g as i32) + (b as i32)
        }
    }
}

1.2.1 match 的高级特性

let x = 7;

// 范围匹配(包含两端)
match x {
    1..=5  => println!("小"),
    6..=10 => println!("大"),
    _      => println!("超出范围"),
}

// 卫表达式(Guard):在模式匹配后加条件
match x {
    n if n > 0 && n % 2 == 0 => println!("正偶数"),
    n if n < 0                => println!("负数"),
    _                          => println!("其他"),
}

// 多重匹配(用 | 合并)
match x {
    1 | 2 | 3 => println!("1~3"),
    4 | 5     => println!("4~5"),
    _          => {},
}

// 解构元组
let point = (3, 4);
match point {
    (0, 0)         => println!("原点"),
    (x, 0)         => println!("X轴: {}", x),
    (0, y)         => println!("Y轴: {}", y),
    (x, y) if x == y => println!("对角线"),
    _               => {},
}

Rust match 的穷举检查

Rust 编译器强制检查 match 是否穷举了所有可能。如果有遗漏,编译器会列出缺少哪些变体,并提示用 _ 作为通配符捕获。这在 C 中是 switch 最大的安全隐患——忘了写 default,程序不会报错,但会产生未定义行为。Rust 在编译期就消灭了这个问题。

1.3 if let 与 while let

if let 是 match 的简化语法,用于只关心一个特定模式的情况:

// match 全写
match maybe_value {
    Some(x) => println!("got: {}", x),
    None     => {},
}

// if let 简化版
if let Some(x) = maybe_value {
    println!("got: {}", x);
}

// while let:持续从迭代器/Option 中取值,直到模式不匹配
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
    println!("popped: {}", top);
}
// stack.pop() 返回 None 时,while 循环结束

1.4 loop 无限循环

loop 是无限循环,它最独特的能力是通过 break value 返回值——这是 C 族语言中根本没有的特性:

// loop + break value:Rust 的"有返回值的无限循环"
let result = loop {
    let random = compute_something();
    if random == 42 {
        break random; // 从 loop 中返回值
    }
    if limit_reached() {
        break -1;     // 也可以返回错误码
    }
};

// 带标签的 break:从深层嵌套循环中直接退出
'outer: for i in 0..10 {
    for j in 0..10 {
        if condition(i, j) {
            println!("Found at ({}, {})", i, j);
            break 'outer; // 直接退出外层循环
        }
    }
}

1.5 for 循环与迭代器

Rust 的 for 循环只能遍历迭代器,不支持 C 式的三段式循环。这体现了 Rust"迭代器即循环"的哲学:

// Rust for: 遍历迭代器
let arr = [10, 20, 30];
for item in arr.iter() {
    println!("{}", item);
}

// 0..n 等价于 C 的 for (i = 0; i < n; i++)
for i in 0..5 {
    println!("i = {}", i); // 0, 1, 2, 3, 4
}

// ..= 是包含末尾的范围
for i in 0..=5 {
    println!("i = {}", i); // 0, 1, 2, 3, 4, 5
}

// 反向遍历
for i in (0..5).rev() {
    println!("i = {}", i); // 4, 3, 2, 1, 0
}

// .enumerate() 同时获取索引和值
for (index, value) in arr.iter().enumerate() {
    println!("[{}] = {}", index, value);
}

设计精髓:Rust 为什么废弃三段式 for?

Rust 社区认为 C 式的三段式 for 循环是"隐藏的微型语言",容易写出 off-by-one 错误和复杂的循环变量更新逻辑。Rust 的 for item in iterator 强制使用迭代器API,让开发者写出更安全的代码。如果确实需要三段式循环,可以用 while + 手动管理的循环变量来模拟。

1.6 运算符分类速查

类别运算符示例说明
算术 + - * / % 5 + 3 整数除法向零取整(Rust),C 向负无穷取整
位运算 & | ^ << >> 0b1010 & 0b1100 逐位操作,右操作数必须是 usize
比较 == != < > <= >= a == b 返回 bool,无隐式转换(比 JS 更严格)
逻辑 && || ! a && b 短路求值,返回 bool(非整数)
范围 a..b a..=b 0..10 Range / RangeInclusive,用于 for 循环和切片索引
类型转换 as i32 as u64 编译期转换,无运行时开销
赋值 = += -= *= /= %= &= |= ^= <<= >>= a += 1 复合赋值,支持所有二元运算符

1.7 格式化输出宏家族

输出流换行返回值典型用途
println! stdout () 标准输出(生产)
print! stdout () 同一行多次打印
eprintln! stderr () 错误信息、调试输出
eprint! stderr () stderr 无换行
format! 不输出(返回 String) 可选 String 字符串插值、拼接
writeln! 任意 Write Result 文件写入、网络发送
dbg! stderr(带文件/行号) 表达式的值 快速调试

1.8 格式化占位符详解

let name = "Alice";
let age = 30;
let pi = 3.14159;

// {}  Display:用户友好输出(需要类型实现 Display trait)
println!("Name: {}", name);     // Name: Alice
println!("Age: {}", age);       // Age: 30
println!("Pi: {}", pi);         // Pi: 3.14159

// {:?} Debug:调试输出(自动为大部分类型 derive)
println!("{:?}", (1, "hello")); // ("hello", 1) — 元组自动 Debug
println!("{:#?}", vec![1, 2]); // [
                                //   1,
                                //   2,
                                // ]

// {:p} 指针地址
let x = 42;
println!("{:p}", &x);          // 0x7fff5fbff8ac

// {:b} / {:x} / {:o} 数进制
println!("{:#b}", 42);          // 0b101010
println!("{:#x}", 42);          // 0x2a
println!("{:#o}", 42);          // 0o52

// {:0>5} 填充对齐
println!("{:0>5}", 42);        // 00042
println!("{:>10}", "hi");      // "        hi"(右对齐)
println!("{:<10}", "hi");      // "hi        "(左对齐)
println!("{:^10}", "hi");      // "    hi    "(居中)

// 命名参数
println!("{name} is {age} years old", name="Bob", age=25);

// 位置参数
println!("{0} + {1} = {2}", 1, 2, 3);

// dbg! 宏
let x = dbg!(2 * 3 + 4); // [src/main.rs:5] 2 * 3 + 4 = 10
// x 的值仍然是 10,但打印了调试信息

格式化参数速记口诀

  • {} → Display(用户输出)
  • {:?} → Debug(调试输出,加 # 变多行缩进)
  • {name} → 命名参数(避免参数顺序混乱)
  • {0}/{1} → 位置参数(显式指定顺序)
  • {:0>5} → 补零右对齐(宽度 5,不足补 0)

二、Why · 为什么这样设计

2.1 为什么 if 是表达式而非语句?

语句只能执行操作,表达式才能求值。Rust 将 if 设计为表达式,让变量初始化可以包含条件逻辑,无需额外的作用域块:

// C 族语言(需要三元运算符或额外作用域):
// int x = (a > b) ? a : b;
// if (a > b) { x = a; } else { x = b; }

// Rust:if 表达式直接返回值
let x = if a > b { a } else { b };

// 更复杂的场景:条件初始化
let config = if std::env::var("DEBUG").is_ok() {
    Config::debug()
} else {
    Config::release()
};
// 无需临时变量,逻辑内聚

2.2 为什么 match 比 switch 更安全?

C 的 switch 有两个主要隐患:忘记写 default(静默忽略未匹配情况)和缺少 break(穿越到下一个分支)。Rust 的 match 消除了这两个问题:

// C 中危险的 switch 穿越(Fall-through):
// switch (x) {
//     case 1: printf("one\n");
//     // 忘记 break!会继续执行 case 2
//     case 2: printf("two\n"); break;
// }

// Rust match:每个分支是独立的,不会穿越
match x {
    1 => println!("one"),
    2 => println!("two"),
    _ => println!("other"), // 必须穷举(或用 _)
}
// Rust 没有 fall-through:每个分支是独立的,不需要 break

C switch 的 fall-through 是一个历史遗留的设计缺陷。它最早来自 BCPL 语言(1967 年),被 C 继承后成为无数 bug 的来源——开发者经常忘记 break,导致逻辑错误。Rust 直接废除了 fall-through,要求每个分支显式处理,消除了这类错误。

2.3 为什么 Rust 不需要三元运算符?

三元运算符 cond ? a : b 的存在理由是"if 作为语句不能返回值"。Rust 的 if 本身就能返回值,所以不需要额外的三元运算符。Rust 社区认为,三元运算符虽然简洁,但可读性差(尤其嵌套时),而且 Rust 的分支可以是多行代码块,表达能力远超三元运算符。

2.4 为什么 Rust 的 for 循环废弃了三段式?

C 式的三段式 for 循环(for (init; cond; update))是 C 语言最灵活也最容易出错的语法之一:

  • 循环变量的作用域容易混淆(init 中声明的变量在循环外仍然存在)
  • update 部分容易被忽略或写错(off-by-one 错误的常见来源)
  • 过于灵活导致"写出奇怪循环"的可能性增加

Rust 用迭代器抽象统一了遍历语义,for item in iterator 强制使用更安全的 API。如果 Rust 觉得迭代器 API 不够用,可以用 while + 手动管理循环变量来模拟三段式。

2.5 为什么 println! 比 printf 更安全?

printf("%s", integer) 在 C 中是未定义行为,但编译器通常不会报错(只是产生垃圾输出)。Rust 的 println! 在编译期通过 fmt trait 系统检查参数类型:如果占位符和参数类型不匹配,编译器直接报错。这让大量运行时格式化错误变成编译期错误。

Rust 格式化系统的安全保障

  • println! → 编译期类型检查(fmt::Display trait)
  • println!("{:?}") → 编译期类型检查(fmt::Debug trait)
  • format! → 同样是编译期检查,返回 String
  • C printf → 运行时解析格式字符串,类型错误在运行时才暴露

三、How · 深层机制与多语言对比

3.1 运算符的多语言对比总表

特性RustC / C++PythonGoJava / JS / TS
三元运算符 无(if 是表达式) c ? a : b a if c else b if c { a } else { b }(无三元) c ? a : b(JS/TS)
整数除法取整方向 向零取整(-3/2 = -1 向零取整(C99+) 向负无穷取整(-3//2 = -2 向零取整 向零取整(JS/TS number)
整数溢出 Debug=panic, Release=wrapping(显式方法可选) 未定义行为(UB) 不溢出(任意精度整数) wrapping wrapping(JS/TS number 是 IEEE-754)
相等比较(== 值比较(PartialEq trait) 值比较(C++ 可重载) 值比较(is 是身份比较) 值比较(无 ==,用 == JS: ==(隐式转换),===(严格)
移位右操作数类型 必须是 usize,超位 panic/wrap 可以是任意整数,超位是 UB 无位运算(用函数) 必须是 unsigned int JS/TS: 右操作数取低 5 位(32 位)
逻辑运算返回类型 bool int(0 或 1) bool bool bool(JS/TS)
赋值表达式返回值 let 是语句,不是表达式 返回赋值的值(a = b 无返回值(Python 3.8+ 的海象运算符有返回值) 无返回值 无返回值
短路求值 && || ??(空值合并) && || and or not && || && ||(JS 还有 ??

3.2 选择控制语句多语言对比

3.2.1 if / else

// Rust: if 是表达式,所有分支类型必须一致
let x = if n > 0 { "positive" } else { "non-positive" };

// C: if 是语句,三元运算符用于需要值的地方
int x = n > 0 ? 1 : 0;

// Python: if 是表达式(Python 3 的 := 可以返回值,但 if/else 表达式语法不同)
x = "positive" if n > 0 else "non-positive"

// Go: if 是语句,没有三元运算符,用 if 块代替
var x string
if n > 0 { x = "positive" } else { x = "non-positive" }

// Java: if 是语句,三元运算符可用
String x = n > 0 ? "positive" : "non-positive";

// JS/TS: 有三元运算符,== vs === 的类型转换问题
const x = n > 0 ? "positive" : "non-positive";

3.2.2 match vs switch

// Rust match: 强制穷举,无 fall-through,卫表达式,多模式
match x {
    1..=5 | 7     => println!("special"),
    n if n % 2 == 0 => println!("even"),
    _               => println!("other"),
}
// C switch: 非穷举,fall-through,case 只能是常量表达式
switch (x) {
    case 1: case 2: case 3: case 4: case 5: case 7:
        printf("special\n");
        break; // 必须手动 break,否则 fall-through
    default:
        printf("other\n");
}
// Go switch: 默认不 fall-through(比 C 更安全),但不是穷举
switch x {
case 1, 2, 3, 4, 5, 7:
    fmt.Println("special")
default:
    fmt.Println("other")
}
// Go 的 switch 也是表达式(可以返回值)
# Python: 没有 switch/match,用 if/elif/else 或字典分发
if x in (1, 2, 3, 4, 5, 7):
    print("special")
elif x % 2 == 0:
    print("even")
else:
    print("other")

# Python 3.10+ 引入 match (结构化模式匹配)
match x:
    case 1 | 2 | 3 | 4 | 5 | 7:
        print("special")
    case n if n % 2 == 0:
        print("even")
    case _:
        print("other")

match vs switch 核心差异总结

  • Rust match:强制穷举、无 fall-through、卫表达式、多模式、返回值、解构
  • C switch:非穷举(可选 default)、fall-through(需手动 break)、仅常量表达式
  • Go switch:非穷举、无 fall-through(默认)、可以是表达式、多个值用逗号分隔
  • Python match:Python 3.10+ 支持结构化模式匹配,语法最接近 Rust,但能力略有不同
  • Java switch:Java 14+ 支持表达式形式(返回值),支持 -> 箭头语法,无 fall-through

3.3 格式化输出多语言对比

语言输出语法类型安全格式化能力
Rust println!("{}", val) 编译期检查 {}(Display)、{:?}(Debug)、{name}(命名)、宽度/对齐/进制
C printf("%d %s", i, s) 无(运行时解析) 宽度、精度、进制(前缀)、浮点格式
C++ std::cout << x << std::endl; 流操作符重载,类型安全 需要 iomanip(setwhex 等)
Python print(f"{x}") f-string 编译期有限检查 f"{x:.2f}"(格式化)、f"{x!r}"(repr)
Go fmt.Printf("%d %s", i, s) 编译期类型检查(Go 1.22+) %v(默认)、%#v(Go-syntax)、%s
Java System.out.printf("%d %s", i, s) 编译期部分检查 格式化字符串(Formatter
JS console.log(`${x}`) 模板字符串,无类型转换检查 模板字符串插值(${}
TS console.log(`${x}`) 模板字符串,类型安全(同 JS) 模板字符串插值
我的理解:Rust 格式化输出 vs Python f-string

Rust 的 println! 和 Python 的 f"hello {x}" 本质上做的是同一件事——把值插入到字符串模板中。但 Rust 的优势在于类型安全(编译期检查占位符和参数类型是否匹配)和格式化能力(宽度、对齐、进制、补零);Python 的优势在于语法更简洁(直接在字符串前加 f)。两者都是现代语言的优秀设计。

3.4 类型转换:as vs From/Into trait

// as:编译期数值类型转换(零成本)
let a: i32 = 42;
let b: u64 = a as u64;          // i32 -> u64,编译期转换
let c: f64 = a as f64;          // i32 -> f64
let d: u8  = a as u8;           // 截断:42 -> 42(但 a > 255 时会截断)

// From/Into:更通用的 trait,用于自定义类型
fn process(data: String) { }

let s: String = String::from("hello"); // From 实现
process(s);                              // Into 自动调用
let s2 = "hello".to_string();           // to_string() 内部调用 From

// 最佳实践:函数参数用 Into<T>(更灵活),具体转换用 as
fn consume_into(s: impl Into<String>) {
    let s = s.into(); // 任意可以转为 String 的类型
}

3.5 实现自定义类型的 Debug 和 Display

use std::fmt;

struct Point {
    x: f64,
    y: f64,
}

// Debug trait:{:?} 输出格式(可 derive,也可以手动实现)
impl fmt::Debug for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Point {{ x: {:.2}, y: {:.2} }}", self.x, self.y)
    }
}

// Display trait:{} 输出格式(必须手动实现)
impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

let p = Point { x: 1.234, y: 5.678 };
println!("{}", p);   // (1.23, 5.68)    — Display
println!("{:?}", p); // Point { x: 1.23, y: 5.68 } — Debug

四、总结

本专题核心要点

  • if 是表达式:所有分支返回同类型值,可以直接赋值给变量;无三元运算符
  • match 模式匹配:强制穷举、无 fall-through、卫表达式、多模式、解构,比 switch 更强大更安全
  • for 遍历迭代器:无三段式语法,用 0..n 或迭代器链式调用实现等效功能
  • 整数溢出:Debug=panic,Release=wrapping,提供了四种显式处理方法
  • println!:编译期类型安全检查,比 printf 更安全;{}(Display),{:?}(Debug),{name}(命名参数)
维度Rust 特色其他语言对比
条件表达式 if 是表达式(无三元运算符) C/C++/JS 有三元运算符;Go 无三元;Python 有 x if c else y
分支穷举 match 强制穷举(编译器检查) C switch 非强制(可选 default);Go/JS/TS 非强制
格式化类型安全 println! 编译期检查(fmt trait) C printf 运行时解析(不安全);C++ iostream 类型安全
整数溢出 Debug=panic,Release=wrapping,显式方法可选 C/C++ UB;Python 不溢出;Go/Java wrapping
运算符重载 通过 trait 实现,不能自定义运算符符号 C++ 自由重载(可在任意位置定义);Python 可以重载任意运算符
循环返回值 loop + break value 可以返回值 C/C++/Go/Python/JS 均不支持循环作为表达式返回值
学完本文后,下一步学什么?
  • 模式匹配进阶:解构嵌套结构体、@ 绑定(n @ 1..=10)、.. 忽略剩余字段
  • 迭代器链式调用.map().filter().fold().collect() 的完整体系
  • 闭包|x| x + 1 的捕获规则、Fn/FnMut/FnOnce trait
  • 自定义格式化:实现 fmt::Formatterwidth()precision() 等方法
  • Error 处理? 运算符传播错误,anyhow/thiserror crate 的惯用法

作者:左扬  |  发表于 2026 年  |  水平有限,欢迎指正

posted @ 2026-06-24 03:34  左扬  阅读(10)  评论(0)    收藏  举报