A.2 函数、语句与表达式(决定返回值的分号)
在第 2 章里,我们已经知道局部变量可以省略类型标注(如 let n = 7;),由编译器自动推断。但一旦走出函数体内部、开始定义函数签名,或者在函数末尾返回计算结果时,Rust 有两条与传统语言截然不同的核心规则。
一、函数签名与「末尾不加分号」的返回值
Section titled “一、函数签名与「末尾不加分号」的返回值”先看下面这个最基础的加法函数 add:
fn-semicolon.rs
// Uncomment the marked line (and comment out `a + b`) to trigger error[E0308].fn add(a: i32, b: i32) -> i32 { a + b // a + b;}
fn main() { let sum = add(3, 4); println!("{sum}");}运行默认源码,输出 7。这里包含两条必须掌握的规则:
1. 函数签名中的参数与返回值类型不能省略
Section titled “1. 函数签名中的参数与返回值类型不能省略”在函数体内部,编译器允许 let sum = add(3, 4); 省略类型标注;但在 fn add(a: i32, b: i32) -> i32 这行函数签名上,所有参数的类型(a: i32, b: i32)以及返回值的类型(-> i32)必须显式写出,绝不允许省略(除非函数不返回有意义的值,此时省略 -> () 默认代表返回单元类型 ())。
这是 Rust 刻意为之的工程设计:只要函数签名把输入和输出的类型实体钉死,调用方只看这一行签名就能完成类型与借用核对,函数体内部的改动不会意外改变对外的接口契约。
2. 表达式(Expression)vs 语句(Statement):那个决定命运的分号
Section titled “2. 表达式(Expression)vs 语句(Statement):那个决定命运的分号”观察 add 函数体的第 3 行:它既没有写 return,末尾也没有加分号 ;,只写了孤零零的 a + b。
如果我们把 a + b 注释掉,取消第 4 行 a + b;(仅仅多加了一个分号)的注释,编译器会立刻拒绝编译:
error[E0308]: mismatched types --> appendix/basic-syntax/src/bin/fn-semicolon.rs:2:27 |2 | fn add(a: i32, b: i32) -> i32 { | --- ^^^ expected `i32`, found `()`3 | // a + b4 | a + b; | - help: remove this semicolon to return this value为什么仅仅多打了一个分号 ;,返回值的类型就从 i32 变成了空值 ()?
因为 Rust 是一门基于表达式(Expression-based)的语言,它严格区分两样东西:
- 表达式(Expression):会计算并产出一个值,末尾不带分号。例如
3 + 4、a + b、一次函数调用add(3, 4)。当一个代码块{ ... }的最后一行是一个不带分号的表达式时,这个表达式算出来的值,就直接成为整个代码块(或整个函数)的返回值。 - 语句(Statement):执行一个动作,但不产出值(或者说只产出空值
())。除了let x = 1;这种变量声明语句之外,只要你在任何一个表达式末尾加上分号;,你就把这个表达式变成了一条语句——刚才算出来的a + b被当场丢弃,整块代码的最终求值结果变成了单元类型(),自然与函数签名要求的-> i32冲突,触发error[E0308]。
(注:Rust 依然保留了 return 关键字,但通常只在需要从函数中途提前返回时写 return val;;函数的正常结尾一律直接写不带分号的尾部表达式。)
二、代码块 {}、if / else 与 loop 本身也是表达式
Section titled “二、代码块 {}、if / else 与 loop 本身也是表达式”理解了「代码块最后一行不加分号就会产出值」,你就能看懂为什么 Rust 的控制流写法如此紧凑:在 Rust 里,代码块 {}、if / else 乃至 loop 循环,全都是可以直接算出一个值的表达式,因此可以直接放在 let x = ... 的等号右侧。
block-and-if.rs
fn main() { let score = 85; let level = if score >= 60 { "pass" } else { "fail" };
let mut n = 1; let doubled = loop { n = n + 1; if n == 4 { break n * 2; } }; println!("{level} {doubled}");}运行程序,输出 pass 8。
if / else作为表达式(替代三元运算符? :): Rust 没有cond ? a : b这种三元运算符,因为if / else自己就能直接产出值。在let level = if score >= 60 { "pass" } else { "fail" };中,两个{}分支内部的"pass"和"fail"末尾都不加分号,作为各自分支的返回值交给变量level(注意:外层最后的;是用来结束整条let level = ...;声明语句的)。 类型约束:当用if / else产出值时,if分支和else分支交出的值必须是完全相同的类型;且不能省略else分支(否则条件不成立时就没有值可交了)。loop循环通过break产出值: Rust 有三种循环:for(遍历迭代器)、while(条件循环)和loop(无条件无限循环)。其中,loop不仅用于死循环,还支持在触发退出时写成break 表达式;(如上面的break n * 2;),把这个值作为整个loop { ... }表达式的计算结果赋给左侧的doubled。
记住一句话,就能彻底避开分号与返回值的报错:想把算出来的值往外吐,末尾就别加分号;加了分号,值就被原地吞掉变成 () 了。