首页 / Rust 入门教程 / 语句与表达式

Rust 入门教程

语句与表达式

本教程共 78 篇 · 第 14 篇 · 更新于 2026-08-08 · 约 7 分钟阅读

RustRust 入门教程语句表达式块表达式隐式返回分号

本节目标:彻底分清”语句不产生值、表达式产生值”这条 Rust 核心规则,理解它如何决定函数返回值,并避开 let 赋值当表达式用的经典错误。

上一章你看到”函数最后一行不加引号就自动返回”。这背后是一条贯穿 Rust 的基础规则:语句和表达式的区别。很多编译错误根源都在这里,所以这章单独讲透。

14-1 两句话定义

  • 语句(statement):执行一个动作,但不产生值。比如 let x = 5; 是语句,它做了”绑定”这件事,但你不拿到任何返回值。
  • 表达式(expression):会被计算成一个值。比如 5 + 6 是表达式,它的值是 11;x + y 是表达式,值是两者之和。

几乎所有编程语言都有这区分,但 Rust 把”表达式产值”贯彻得特别彻底:函数调用是表达式、宏调用是表达式、甚至由 {} 包起来的代码块也是表达式。

14-2 分号把表达式变成语句

关键规则:表达式后面加个分号,它就变成了语句,于是不再产值。

let y = 6;        // 语句(let 本身是语句)
let x = (let y = 6); // 错!let 是语句,不能放在等号右边

第二行会报错,因为 let y = 6 是语句、不产值,不能赋给 x。这跟 JavaScript 的 let x = (let y = 6) 完全不同,Rust 不允许这么写。

14-3 函数返回值来自末行表达式

回到上一章的例子:

fn add(x: i32, y: i32) -> i32 {
    x + y
}

x + y 是个表达式,值是两数之和。因为它处在函数体最后、且没打分号(仍是表达式),它的值就被当作整个函数的返回值。一旦你写成:

fn add(x: i32, y: i32) -> i32 {
    x + y; // 加了分号 -> 变成语句 -> 不产值
}

编译器立刻报:函数声明返回 i32,实际返回的是 ()(单元类型)。这就是”分号改变返回值”的直接后果。

Warning

这是新手最高频的困惑之一:“我就多了个分号,怎么返回值没了?“记住:函数里想返回的那行,绝不加尾分号。加了的就不是返回值了。

14-4 块表达式(block expression)

用花括号 {} 包起来的一段代码,本身也是一个表达式,它的值是块里最后一个表达式的值:

fn main() {
    let x = 5;
    let y = {
        let inner = 3;
        inner + x // 没分号,块的值是 8
    };
    println!("y = {y}"); // 输出 8
}

{ let inner = 3; inner + x } 这个块是表达式,它的值是 inner + x 即 8,赋给了 y。块里最后一行加分号就不产值了。这种”块当值用”的写法在 Rust 里极常见,尤其配合 if 表达式(下一章会看到 let y = if cond { 1 } else { 2 };)。

Note

注意 let y = { ... }; 整体是语句(因为有最外层的分号),但等号右边的 { ... } 是块表达式、产值 8。区分”块表达式产值”和”整个 let 语句不产值”,是理解的关键。

14-5 为什么 Rust 这么设计

把一切都当表达式,能让代码更紧凑、更函数式。比如不用写 if 里赋值、外面再读,而是直接 let result = if ... { ... } else { ... }; 一步到位。代价是初学者要适应”分号决定有没有返回值”这条铁律。

Tip

一个实用心法:当你希望某行”算出一个值交给别人用”,就别在它末尾打分号;当你只是”执行一个动作”(打印、赋值、调用一个不在乎返回的函数),就打分号。慢慢就形成肌肉记忆。

14-6 宏也是表达式

println!("hi") 这种宏调用也是表达式,值是单元类型 ()。所以它既可以当语句(加分号),其 () 值也能出现在需要 () 的地方。你平时在 main 里写 println!(...); 加分号当语句用,完全没问题。

14-7 把规则串起来看

  • let x = 5; → 语句,不产值。
  • x + y → 表达式,值 = 两数之和。
  • foo() → 表达式,值 = 函数返回值。
  • { ... } → 块表达式,值 = 块末行表达式值。
  • 上述任意表达式加 ; → 变语句,失去返回值。

函数返回值 = 函数体最后一个表达式的值。这一条把前面所有概念串起来了。

Warning

别试图把 let 当表达式用在赋值右侧,也别在想返回的行尾乱加分号。这两类错误占了新手”返回值不对”问题的绝大多数。

14-8 把 if 当表达式赋值

把前面学的块表达式和 if 结合,能写出很紧凑的赋值。因为 if 是表达式,整体能产出一个值,直接绑给变量,不必先声明再在分支里改:

fn main() {
    let a = 10;
    let b = 20;
    let bigger = if a > b { a } else { b };
    println!("较大的是 {bigger}");
}

if a > b { a } else { b } 是表达式,较大的那个数成了 bigger 的值。注意两个分支末行都别打分号。这种写法在别的语言里常要写成 let bigger; if ... { bigger = a } else { bigger = b };,Rust 一行就搞定,也更不容易漏赋值。

Tip

凡是”根据条件挑一个值”,优先考虑 let x = if cond { ... } else { ... }; 这种表达式写法,而不是用 mut 变量在分支里反复赋值。前者保证 x 一定有值,后者可能漏掉某个分支导致 x 未初始化(编译器还会报错提醒你)。

14-9 表达式文化的来由

为什么 Rust 这么强调”一切皆表达式”?因为它鼓励函数式风格:多用”算出值”代替”修改状态”。一个表达式求值得到值,天然没有副作用;而一条语句只做事、不产生值,往往意味着它在改某个外部状态。多写表达式、少写语句,代码更容易推理、更容易测试。

// 偏命令式:改状态
let mut max = a;
if b > max { max = b; }

// 偏表达式:算值
let max = if a > b { a } else { b };

两种都得到最大值,但下面那种保证 max 一定有值、且没动其它变量。这正是表达式文化的好处。

Note

你不必强求每行都写成表达式,但理解”分号决定有没有返回值”之后,你会自然地写出更紧凑、更安全的代码。这是 Rust 和”语句式”语言在思维上的一个小分岔。

14-10 早返回与表达式风格

“早返回(early return)“是配合表达式风格的常见写法:在函数开头用 if 不合法 { return; } 把异常情况挡在门外,剩下的代码全在标准路径上,不用层层 if 缩进。它和”语句 vs 表达式”的关系在于:早返回让主逻辑保持线性、可读,而表达式(如 let x = if ... {})让”算值”代替”改状态”。

两种风格结合,能写出又短又清晰的代码。新手常把所有逻辑塞进一个 if/else 大块里,结果右边越缩越深。试着把”不符合前提就先 return”,函数体立刻瘦身。

Note

早返回不是强制的,有些团队偏好”单一出口”(函数只有一个 return)。但 Rust 社区普遍接受早返回,因为它确实减少嵌套。你按自己顺手来,关键是避免深层缩进的”金字塔”代码。

14-11 小结

语句执行动作不产值,表达式计算产值;分号让表达式降级为语句;函数末行表达式即返回值;块 {} 也是表达式。吃透这几点,你对 Rust 的代码阅读和报错理解会上一个大台阶。下一章讲注释与文档注释,看看怎么给代码写说明、以及怎么自动生成 API 文档。