Rust 程序结构
Rust 程序如何从 crate 入口、模块、语句与表达式组织起来;main 二进制与库 crate 的基本骨架。
#type / concept
#status / growing
#tech / dev
#resource / rust
[!info] 关联笔记
- 所属 MOC:语言基础 · 学习路线
- 前置概念:安装并运行第一个程序
- 后续概念:函数、模块、crate 与可见性、Cargo
- 容易混淆:运算符与表达式、流程控制
Rust 程序结构
这个概念为什么出现
能 cargo run 之后,下一步要回答:
- 程序从哪里开始执行?
- 二进制 crate 与库 crate 有何不同?
- 为什么“最后一行没有分号”会变成返回值?
- 代码如何拆到模块而不靠把所有东西塞进
main?
没有程序结构心智,后续所有权与模块可见性都会显得像随机语法。
[!abstract] 一句话理解 二进制 crate 从
fn main启动;Rust 是表达式语言——块与多数控制流产生值;更大的组织靠 crate/module,而不是把逻辑堆在全局脚本里。
最小可运行示例
场景:计价服务启动时打印版本,并计算一笔本地试算
服务进程启动要做两件事:打印构建标识,以及对一笔样例订单做本地试算。
用函数拆分“启动日志”与“纯计算”,体会语句(有分号)与表达式返回。
// 业务意图:
// 1. 启动时打印服务标识;
// 2. 用纯函数计算订单应付金额(分)。
// 教学点:
// - main 是二进制入口;
// - 块/函数体最后表达式作为返回值(无分号);
// - 语句以分号结束,丢弃值。
fn service_banner() {
// 语句:调用宏,不需要返回有意义的值给调用方
println!("order-pricing ready");
}
// 计算应付:单价 * 数量,返回 i64
fn line_total(unit_price_cents: i64, qty: i64) -> i64 {
// 最后一行无分号:这是返回值表达式
unit_price_cents * qty
}
fn main() {
service_banner();
let total = {
// 块本身是表达式:可在块内准备中间量
let unit = 199_i64; // 1.99 元 → 199 分
let qty = 3_i64;
line_total(unit, qty) // 块的值
};
println!("sample total_cents={}", total);
}
建议运行:
cargo new program_structure_demo && cd program_structure_demo
# 将上述代码写入 src/main.rs
cargo run
期望输出:
order-pricing ready
sample total_cents=597
结合场景再看三个关注点
- 入口单一清晰:运维看进程,开发者看
main编排,计算逻辑可单测。 - 表达式返回减少临时变量噪音:但团队仍可显式
return提高可读性。 - 块表达式适合“局部算完就得到值”,与所有权场景中的短作用域绑定相得益彰。
核心概念与准确模型
1. Crate:编译单元
- 二进制 crate:产出可执行文件,通常有
src/main.rs - 库 crate:产出库,通常有
src/lib.rs - 一个 package(
Cargo.toml)可含多个 target(bin/lib/example/test)
细节见 Cargo、模块、crate 与可见性。
2. 入口
- 二进制:
fn main() - 库:对外暴露的是公共 API,没有
main;测试或 bin 去调用它
3. 语句 vs 表达式
| 形态 | 特征 | 例子 |
|---|---|---|
| 语句 | 执行动作,不产生可用值(或值被分号丢弃) | let x = 1;、println!(...) |
| 表达式 | 求出值 | 1 + 2、if ...、{ ... }、函数调用 |
函数体:
fn add(a: i32, b: i32) -> i32 {
a + b // 表达式返回
}
等价显式:
fn add(a: i32, b: i32) -> i32 {
return a + b;
}
4. 模块雏形
src/
main.rs # 或 lib.rs
pricing.rs # mod pricing;
main.rs:
mod pricing;
fn main() {
let _ = pricing::line_total(10, 2);
}
可见性默认私有,跨模块需 pub(阶段五深化)。
设计动机
- 显式入口降低“脚本顶层乱执行”的可预测性成本
- 表达式语言让控制流与返回值统一,减少“只能用临时变量”的噪声
- crate/module 把封装做进语言,而不是靠文件约定 alone
边界情况与反直觉行为
let是语句:let x = 1本身不是表达式值(let语句不返回被绑定的值给外层)。- 分号改变意义:
a + b;在需要返回值的位置会变成返回()。 main也可返回Result:便于?传播,失败时以非零退出(版本/细节以当前书与 Reference 为准)。
常见误区
[!warning] 常见误区:所有逻辑都写在 main 错误理解:main 是唯一函数。
正确模型:main 编排,领域逻辑函数化/模块化,利于测试。
为何易错:教程示例为短小而堆在 main。
工程实践
- bin 只做注入与启动;核心逻辑放 lib,便于
cargo test。 - 模块按领域切,不按“技术层级无限文件”。
- 对“返回
()却声明了类型”的错误,先查分号。
本节总结
- 二进制从
main进入;库暴露 API。 - 语句执行,表达式产生值;无分号最后一行常作返回值。
- 模块与 crate 是结构扩展点,后续与可见性一起学。
自测题
概念题
- 为什么
fn f() -> i32 { 1; }往往编译失败?
代码推理题
- 块
{ let x = 2; x + 1 }的值是什么?
参考答案
- 分号使块值变成
(),与i32返回类型冲突。 3。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| The Book — Getting Started / Common Concepts | 官方书 | 程序与函数结构 |
| Cargo Book — Project Layout | 官方 | 目录约定 |