文档【免费下载链接】rfcsRFCs for changes to Rust项目地址https://gitcode.com/gh_mirrors/rf/rfcs点击查看免费下载导读本文以 Rust RFC 仓库中的 RFC 911《const fn》 为核心系统梳理const fn引入的动机、语法设计与常量表达式求值规则并结合 RFC 1414rvalue 提升、RFC 2341const 局部变量、RFC 2342const 控制流、RFC 2344const 循环、RFC 2345const panic、RFC 2203const 重复表达式、RFC 2920内联 const等后续 RFC展示从极简最小改动到现代编译期求值CTFE体系的完整演进路径。读完本文你将理解const fn的边界约束什么能写、什么不能写、它在原子量/线程局部变量等场景的实战用法以及它如何逐步打开 Rust 编译期计算的大门。一、RFC 911 诞生背景常量上下文的安全债在const fn出现之前Rust 的常量上下文const/static初始化器无法调用任何用户自定义函数。为了让原子类型、互斥锁等类型能够在const/static中初始化标准库被迫走了一条危险的捷径直接暴露UnsafeCell的内部字段。RFC 911 原文给出的例子极具代表性#[langunsafe_cell] struct UnsafeCellT { pub value: T } struct AtomicUsize { v: UnsafeCellusize } const ATOMIC_USIZE_INIT: AtomicUsize AtomicUsize { v: UnsafeCell { value: 0 } };这种做法的代价是双重的脆弱且不可组合UnsafeCell本该封装数据字段却被迫公开。若要初始化一个值为usize::MAX的AtomicUsize静态变量就得为每个可能取值单独写一个const。抽象缺失导致滥用AtomicPtrT、CellT这类类型在常量上下文中完全没有初始化途径社区只能退而使用UnsafeCell或危险的static mut抛弃类型安全与封装。RFC 还点名了一个重灾区实现期间的std::thread_local。其内部std::thread_local::imp::Key的所有字段都被迫公开以便宏填充并且因为宏内缺乏稳定性卫生stability hygiene这些字段甚至被标记为 stable。围绕移除危险且常被误用的static mut的 pre-RFC 曾获得正面反馈但前提是必须能在const和static中创建并使用的抽象。此外size_of等内建函数intrinsic也需要进入常量表达式包括定长数组类型。与逐个引入关键字的方案相比const fn提供了一个可扩展、可组合的统一积木。RFC 的定位非常克制——它明确声明本 RFC 的意图是引入一个最小改动使得安全的抽象写法能与常量上下文之外的普通代码相似它明确不引入通用的 CTFE编译期函数求值机制。二、语法设计自由函数与固有方法标记constRFC 911 的核心设计只有一条规则自由函数和固有方法inherent method可以标记为const。const fn foo(x: T, y: U) - Foo { stmts; expr } impl Foo { const fn new(x: T) - Foo { stmts; expr } const fn transform(self, y: U) - Foo { stmts; expr } }几个关键限制需要特别注意trait、trait 实现及其方法不能是const。RFC 特意留出这条边界以便未来设计一个能与 trait 良好交互的 constness/CTFE 体系详见下文Alternatives中的consttrait 构想。参数只允许简单的按值绑定by-value binding如x: T。按引用绑定与解构虽可支持但没有必要且只会让实现复杂化——这一限制直到 RFC 2341 才被解除。函数体按const块来检查等价于把它当作一个const项的初始化器const FOO: Foo { // 当时只允许item 语句出现在这里 stmts; // 函数参数与常量表达式可以自由组合 expr }三、常量表达式求值规则一份被补全的形式化清单由于当时const项尚无正式规范RFC 911 顺势把常量值纯编译期常量的规则显式化而不是让它们保持隐式。这份清单是理解const fn能力边界的关键规则维度具体内容已实现的表达式集合原始字面量ADT元组、数组、结构体、枚举变体原始类型上的一元/二元运算强制转换cast字段访问/索引无捕获闭包引用块仅 item 语句 尾表达式副作用禁止赋值、非const函数调用、内联汇编inline asmDrop类型结构体/枚举值若其类型实现了Drop则不允许但该限制不传递例如创建None::VecT是允许的完全无害。RFC 顺带指出这条规则未来可用于允许[x; N]对非Copy的x生效后来由 RFC 2203 实现引用引用真正不可变任何具有内部可变性的值都不能放在引用后面可变引用只能从零尺寸ZST值创建如mut || {}——这样引用只需由值本身表示不承诺实际内存地址裸指针只能由整数、引用或其他裸指针创建不能解引用也不能转回整数——因此任何常量裸指针都能用常量整数或常量引用表示循环无副作用意味着循环只会影响终止性没有实用价值故不实现条件分支if/else与match同样未实现其中match是唯一有实现难度的let绑定不可变let绑定的实现难度与if/else相当且缺乏需求块最初引入const/static只是为了给全局初始化器里临时用的 item 提供作用域这段规则描述与 RFC 1229《compile_time_asserts》 中定义的常量求值上下文const/static初始化器、数组长度、重复表达式长度、类 C 枚举判别值、模式共同构成了早期 CTFE 的边界。四、实战示例const fn在常量/静态上下文中的调用RFC 给出了完整的独立示例展示了const fn在常量上下文中被任意调用的能力——注意其中方法链、数组索引、以及static/#[thread_local] static的组合使用// Standalone example. struct Point { x: i32, y: i32 } impl Point { const fn new(x: i32, y: i32) - Point { Point { x: x, y: y } } const fn add(self, other: Point) - Point { Point::new(self.x other.x, self.y other.y) } } const ORIGIN: Point Point::new(0, 0); const fn sum_test(xs: [Point; 3]) - Point { xs[0].add(xs[1]).add(xs[2]) } const A: Point Point::new(1, 0); const B: Point Point::new(0, 1); const C: Point A.add(B); const D: Point sum_test([A, B, C]); // Assuming the Foo::new methods used here are const. static FLAG: AtomicBool AtomicBool::new(true); static COUNTDOWN: AtomicUsize AtomicUsize::new(10); #[thread_local] static TLS_COUNTER: Cellu32 Cell::new(1);这段代码同时呼应了 RFC 的动机AtomicBool::new(true)、AtomicUsize::new(10)、Cell::new(1)这些现代写法正是当年UnsafeCell { pub value: ... }直接暴露字段问题的最终解法——原子类型与Cell从此可以通过const fn new获得类型安全的常量初始化static mut的滥用场景被大幅压缩。五、类型参数与 trait可以引用但不能调用const fn的类型参数及其约束不受限制但由于 trait 方法在此设计中永远不可能是const因此无法在常量求值中调用 trait 方法。不过取用 trait 方法仍然有用——可以把它们转成函数指针const fn arithmetic_opsT: Int() - [fn(T, T) - T; 4] { [Add::add, Sub::sub, Mul::mul, Div::div] }这里Add::add等只是被取地址为fn(T, T) - T并未在编译期执行因此不违反规则。这类把 trait 方法物化为函数指针的写法是早期const fn在泛型世界里为数不多的组合手段之一。六、const unsafe fn在常量中构建不变量const fn也可以是unsafe的。这对于需要维持内部不变量的类型尤其重要——例如std::ptr::Unique要求指针非空struct OptionalInt(u32); impl OptionalInt { /// Value must be non-zero const unsafe fn new(val: u32) - OptionalInt { OptionalInt(val) } }注意本 RFC 在文件末尾的Updates since being accepted中明确记录了接受后新增了const unsafe fn的允许。它让通过unsafe接口保证构造过程满足安全条件这一惯用法从运行时延伸到了编译期。七、rvalue 提升参数被视为可能变化RFC 911 为 rvalue promotion提升到静态内存定了一条重要规则出于 rvalue 提升的目的const fn的参数被视为可能变化potentially varying因为该函数仍然可能在运行时以非常量参数被调用。这条规则直接影响了后来的常量提升设计。RFC 1414《rvalue static promotion》 系统化了把常量表达式右值提升到静态内存、从而获得static引用的机制并给出与const fn一致性的表述如果const X: static T constexpr能编译那么let x: static T constexpr也应能编译。其UnsafeCell限制被提升的值必须真正不可变与 RFC 911 引用是真正不可变的内部可变性不能放在引用后面的规则一脉相承。此后 RFC 3027《infallible promotion》 进一步把隐式提升收窄到必然成功的操作并将const fn调用从可隐式提升的集合中剔除因为用户自定义const fn的求值可能以多种方式失败。这也印证了 RFC 911 当年把参数视为可能变化的先见之明——const fn的可失败性从一开始就是提升机制必须面对的问题。八、从 2015 到现代const fn能力演进时间线RFC 911 于 2015-04-06 被接受。它在Unresolved questions中留下的多个悬念——递归、let绑定、if/else——随后被一系列 RFC 逐一落地时间点/RFC落地的能力RFC 1440允许带析构器的类型用于static/const/const fnstatic不运行析构器、const在使用点运行析构器仍禁止需要运行析构器的常量求值如const fn内drop(foo)RFC 1414rvalue 提升42可得到static u32常量表达式自动进入静态内存RFC 2203重复表达式[x; N]允许x为可提升的 const 值不再要求Copy如const arr: [OptionBoxu32; 100] [X; 100]RFC 2341常量与const fn体内允许let绑定与解构基于 miri 新求值器参数解构也随之放开RFC 2342常量求值支持if/match并惰性求值NonZero::new等成为const fn成为可能RFC 2344支持loop/while/while letfor因Iterator::next非const暂不可用MIR 解释器以终止子计数 时间戳方式防死循环RFC 2345panic!/assert!/assert_eq!可用于常量编译期触发时以error[E0080]形式报告RFC 2920内联const块const { ... }表达式可在表达式与模式中使用显式声明此处编译期执行以 RFC 2342 为例其动机直接引用了让NonZero::new成为 const fn和解释断言两个需求它同时展示了const fn化解急切求值问题的价值——把常量写成带参数的函数const X: u32 ...; const Y: u32 ...; const fn foo(x: u32, y: u32) - u32 { if x y { x - y } else { y - x } } const AB: u32 foo(X, Y);由于foo的参数未知而无法被急切求值只有实际传入参数后才会求值被选中的那一个分支。这正是 RFC 911参数被视为可能变化设计在控制流时代的延续。RFC 2344 则提供了const fn内命令式循环的经典范例斐波那契const fn fib(n: u128) - u128 { let mut a 1; let mut b 1; let mut i 2; while i n { let tmp a b; a b; b tmp; i 1; } b }从递归指数时间到循环线性时间const fn的实用性随控制流支持而质变。九、原设计的 Drawbacks 与 AlternativesDrawbacksRFC 911 只列了一个风险不够保守的设计可能制造向后兼容性隐患这些隐患要等到 1.0 之后更全面的 CTFE 提案出现时才暴露。Alternativesconsttrait 的构想虽然 RFC 明确规定 trait 方法不能是const但作者在 Alternatives 中描述了一个未经验证的扩展方向用const限定 trait 实现与 trait 约束。这对运算符重载 trait 的有意义交互是必要的const fn map_vec3T: Copy, F: const Fn(T) - T(xs: [T; 3], f: F) - [T; 3] { [f([xs[0]), f([xs[1]), f([xs[2])] } const fn neg_vec3T: Copy const Neg(xs: [T; 3]) - [T; 3] { map_vec3(xs, |x| -x) } const impl Add for Point { fn add(self, other: Point) - Point { Point { x: self.x other.x, y: self.y other.y } } }RFC 的分析很有层次consttrait 方法所有实现均为const看似有用但单独使用无法支撑上述场景而带有const方法的 trait 实现而非整个impl为const虽然允许直接调用但如何写出一个泛型函数、要求某 trait 的某个方法以const实现在当时并不清晰。这个方向至今仍是 Rust 语言设计中的开放话题——例如现代const求值虽已成熟const泛型 trait 方法仍需显式的 const 化设计。十、遗留问题递归的取舍RFC 911 的 Unresolved questions 聚焦于一个至今仍值得玩味的决策const fn是否允许递归保守选择禁止递归不会影响本 RFC 的目标用例若允许则大概率需要递归深度上限或至少能处理尾递归的求值算法在当时由于常量中没有控制流原语实际上根本写不出递归const fn——但这一点不能想当然至少if/else应当最终可用。这个判断被历史验证随着 RFC 2342if/match与 RFC 2344循环的落地递归const fn不仅成为可能还成为循环之外的另一种常见编译期算法表达。而现代 rustc 也确实为常量求值设置了求值步数上限防止无限递归/无限循环挂起编译器。十一、结语从最小改动到 CTFE 体系的基石回看 RFC 911它最初只是把UnsafeCell的字段收回去而提出的最小语法改动允许自由函数与固有方法标记const以const块规则检查函数体参数按值传递无控制流、无 trait 方法调用。但正是这个克制到极点的设计为 Rust 此后十年的编译期求值能力提供了可扩展的骨架语法上const fn与普通函数无差别让把运行时函数改成常量函数成为零成本的迁移路径语义上参数可能变化的假设与严格的不可变引用规则为 RFC 1414、RFC 2203、RFC 3027 的提升机制划定了安全边界生态上AtomicBool::new(true)、Vec::new()作为const fn的普及让static mut和裸UnsafeCell暴露字段的旧时代彻底终结。今天的 Rust 开发者可以放心地在const fn中写局部变量、if/match、while循环甚至panic!——这些能力看似理所当然但它们的每一块基石都始于 2015 年那份刻意保持最小改动的 RFC 911。赞分享文档【免费下载链接】rfcsRFCs for changes to Rust项目地址https://gitcode.com/gh_mirrors/rf/rfcs点击查看免费下载相关推荐仓颉语言常量求值const 函数、const 表达式与 const init 编译期求值指南仓颉语言常量求值const 函数、const 表达式与 const init 编译期求值指南 本文基于仓颉开发者文档的 const 函数和常量求值章节 htt文档编程语言Rust 中的 const 常量编译期求值、const fn 与内联机制comprehensive-rust 实战解析Rust 中的 const 常量编译期求值、 const fn 与内联机制comprehensive rust 实战解析 本篇技术指南围绕 Google文档教程The Concise TypeScript Book从模块导出值推断类型Type from Module实战指南The Concise TypeScript Book从模块导出值推断类型Type from Module实战指南 导读 本篇文章聚焦 The Conc文档上一篇PromiseKit简化异步编程的强大工具下一篇CSSTree 教程解析、验证和操作CSS语法创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考