const fn 的限制与能力?

解读

在国内 Rust 岗位面试中,const fn 是区分“写过业务”与“读过标准库源码” 的试金石。
面试官通常不会只问“能做什么”,而是追问“为什么不能做某事”,借此考察候选人对编译期求值(CTFE)与运行时语义的边界理解。
回答时务必先给能力、再给限制、最后给版本演进,体现你紧跟语言发展,避免“Rust 2018 答案”这种减分点。

知识点

  1. 能力(What you can do)

    • 在编译期求值,生成真正的编译期常量(const 上下文可用)。
    • 允许调用其他 const fn内置 const 运算符(如算术、位运算、短路逻辑)。
    • 可使用 let 绑定、if/while、? 运算符、panic!、assert!(1.57+ 起)。
    • 支持 基本控制流(loop、for、match、break 带值),但需保证可静态展开。
    • 允许 调用 const unsafe fn,但函数体本身仍需满足 const 安全要求。
    • 从 1.61 起可在 trait 中声明 const fn;1.64 起支持 impl const Trait for T 的 const 上下文特化。
  2. 限制(What you still cannot do)

    • 不能分配堆内存(Box、Vec、String::new() 均不可用)。
    • 不能调用非 const fn(包括大部分 std 与第三方 API)。
    • 不能产生悬垂引用(生命周期必须在编译期可解析)。
    • 不能进行动态分发(trait object、dyn Trait 被禁止)。
    • 不能访问静态变量(static 与 static mut 均不可读不可写)。
    • 不能执行 I/O(文件、网络、环境变量、系统时间、随机数)。
    • 不能转换裸指针为引用(as_ref / slice::from_raw_parts 均受限)。
    • 不能包含内联汇编(asm! 宏被禁止)。
    • 不能触发未定义行为(UB 在 const 上下文直接报错,而非运行时)。
  3. 版本演进(加分项)

    • Rust 1.46 前:const fn 体几乎为空,只允许单表达式。
    • 1.48:引入 const 循环与基本控制流。
    • 1.56:允许 panic!。
    • 1.57:允许 assert!、assert_eq!。
    • 1.59:允许 trait 中的默认 const fn。
    • 1.61:允许 const fn in trait。
    • 1.64:允许 impl const Trait。
    • 1.68:允许 const 引用到 static 的借用(仍需 'static 生命周期)。

答案

const fn 的核心能力是在编译期完成求值并产出真正的编译期常量,从而把运行时代价降为零。
它能使用算术、位运算、控制流、panic、assert以及调用其他 const fn;同时受限于无堆分配、无 I/O、无动态分发、无静态变量访问、无未定义行为等规则。
随着版本迭代,Rust 正逐步放宽限制,但任何可能引入运行时环境依赖或 UB 的操作仍被禁止,以保证 const 求值的可预测性与可复现性。

拓展思考

  1. const fn 与 inline const {} 的协同
    1.79 起稳定化的 inline const 块允许在非 const 上下文里强制编译期求值,可把复杂初始化逻辑从宏转移到类型系统,减少 proc-macro 依赖

  2. const 泛型 + const fn 的实战
    利用 const fn 计算数组长度或位掩码,再喂给 const 泛型参数,可实现零成本静态配置表,在嵌入式与区块链领域被广泛用于生成查找表(LUT)与哈希常量

  3. const 求值的副作用边界
    虽然 const fn 不能读写 static,但可通过 const 上下文实例化单例(如 OnceCell::new() 的 const 构造函数)实现编译期注册表,为驱动与内核模块提供**“编译期即完成链接”**的能力,这是 Linux Rust 模块实验中的关键技巧。