Rust 程序结构

Rust 程序如何从 crate 入口、模块、语句与表达式组织起来;main 二进制与库 crate 的基本骨架。

#type / concept #status / growing #tech / dev #resource / rust

[!info] 关联笔记

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

结合场景再看三个关注点

  1. 入口单一清晰:运维看进程,开发者看 main 编排,计算逻辑可单测。
  2. 表达式返回减少临时变量噪音:但团队仍可显式 return 提高可读性。
  3. 块表达式适合“局部算完就得到值”,与所有权场景中的短作用域绑定相得益彰。

核心概念与准确模型

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 + 2if ...{ ... }、函数调用

函数体:

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。

工程实践

  1. bin 只做注入与启动;核心逻辑放 lib,便于 cargo test
  2. 模块按领域切,不按“技术层级无限文件”。
  3. 对“返回 () 却声明了类型”的错误,先查分号。

本节总结

  • 二进制从 main 进入;库暴露 API。
  • 语句执行,表达式产生值;无分号最后一行常作返回值。
  • 模块与 crate 是结构扩展点,后续与可见性一起学。

自测题

概念题

  1. 为什么 fn f() -> i32 { 1; } 往往编译失败?

代码推理题

  1. { let x = 2; x + 1 } 的值是什么?
参考答案
  1. 分号使块值变成 (),与 i32 返回类型冲突。
  2. 3

延伸阅读与资料来源

资料类型支撑内容
The Book — Getting Started / Common Concepts官方书程序与函数结构
Cargo Book — Project Layout官方目录约定
创建于 2026/7/15 更新于 2026/7/15