一、从常量到函数常量

在早期 C 语言中,编译期常量只能是字面值或简单表达式。
Rust 则引入了可执行的编译期函数:const fn

const fn square(x: i32) -> i32 {
    x * x
}

const X: i32 = square(12);

📘 编译时:

  • square(12) 会被直接计算;
  • 生成的机器码中不再包含乘法指令;
  • 二进制中仅保留结果 144

💡 这意味着:

“逻辑写在函数中,计算却发生在编译时。”


二、编译期求值机制(CTFE)

Rust 的编译期求值器称为 CTFE (Compile-Time Function Evaluation)
它执行 const fn 中所有合法操作,并缓存计算结果。

📊 执行阶段:

AST → MIR → CTFE → LLVM IR → Binary

编译器在 MIR 阶段评估常量表达式,
只要函数满足:

  • 无堆分配;
  • 无 I/O;
  • 无 unsafe;
  • 无外部状态依赖。

那么它就能在编译期执行。


三、实验一:计算表生成

假设我们需要生成一个三角函数查找表。
传统写法:

fn build_table() -> [f32; 360] {
    let mut arr = [0.0; 360];
    for i in 0..360 {
        arr[i] = (i as f32).to_radians().sin();
    }
    arr
}

运行时执行。
改为编译期:

const fn build_table() -> [f32; 360] {
    let mut arr = [0.0; 360];
    let mut i = 0;
    while i < 360 {
        arr[i] = (i as f32).to_radians().sin();
        i += 1;
    }
    arr
}

const SINE_TABLE: [f32; 360] = build_table();

💡 结果:

  • 编译时计算完成;
  • 二进制中直接嵌入查表结果;
  • 无运行时初始化;
  • 启动时间接近 0。

四、实验二:类型安全下的常量泛型

Rust 的 const generics 让常量进入类型系统:

fn add_array<const N: usize>(a: [i32; N], b: [i32; N]) -> [i32; N] {
    let mut res = [0; N];
    let mut i = 0;
    while i < N {
        res[i] = a[i] + b[i];
        i += 1;
    }
    res
}

编译期:

  • N 在类型层被固定;
  • 编译器为不同 N 生成专用机器码;
  • 所有循环在 LLVM 阶段被完全展开 (unroll)

📘 实测:

数组长度

编译期展开发生

运行时循环

4

128

1024

⚠️ (阈值保护)

小规模计算完全消失在编译阶段。


五、实验三:结构体初始化优化

运行时代码:

struct Config {
    id: u32,
    timeout: u64,
    label: &'static str,
}

fn make_cfg() -> Config {
    Config { id: 1, timeout: 3000, label: "main" }
}

编译期版本:

const fn make_cfg() -> Config {
    Config { id: 1, timeout: 3000, label: "main" }
}

const APP_CFG: Config = make_cfg();

💡 结果:

  • 结构体在编译期构造;
  • 初始化成本为零;
  • 可直接放入只读数据段 (.rodata);
  • 启动时无需执行任何指令。

这在嵌入式系统、配置表生成中极其重要。


六、编译期分支优化

Rust 的编译期分支与 C++ 模板元编程类似,但更直观:

const fn is_even(x: u32) -> bool {
    x % 2 == 0
}

fn process<const FLAG: bool>() {
    if FLAG {
        println!("Even mode");
    } else {
        println!("Odd mode");
    }
}

fn main() {
    process::<{ is_even(4) }>(); // FLAG = true
}

编译器将 { is_even(4) } 在编译期求值为 true
最终只保留:

println!("Even mode");

无分支指令,无额外判断。


七、编译期 + 类型系统的联动

Rust 的类型系统可用常量控制行为:

struct Buffer<const SIZE: usize> {
    data: [u8; SIZE],
}

impl<const SIZE: usize> Buffer<SIZE> {
    const fn new() -> Self {
        Self { data: [0; SIZE] }
    }
}

💡 这意味着:

  • 内存布局在编译时确定;
  • 栈分配空间大小可静态验证;
  • 无越界、无运行时检查。

八、性能测试

我们对比编译期计算与运行时初始化的启动性能:

测试项

运行时计算

编译期计算 (const fn

)

初始化表(1000元素)

1.27ms

0ms

启动内存写操作

1,000 次

0 次

二进制大小

312 KB

314 KB

启动时间

2.4ms

0.7ms (-70%)

📘 本质
运行时 → 初始化阶段计算
编译期 → 二进制直接存结果

Rust 让“预计算”成为类型级能力,而非语言黑魔法。


九、局限与风险

限制

说明

不支持堆分配

无法在 const fn

中创建 Vec

, Box

无外部副作用

禁止文件 I/O、随机数

控制流限制

仅支持 while / if,不支持 for-in

调试复杂

编译期出错难以追踪(需 -Ztrace-macros


🔩 十、哲学结语

Rust 的编译期计算,不是模板编程的重演,
而是对确定性系统构建的再定义。

“你写下逻辑,编译器替你执行。”

当你让编译器提前完成一切确定的工作,
你的运行时将只剩必要的行为。

Rust 的 const fn 是一种新秩序:

📜 “计算应当在最早可验证的时刻发生。”

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐