尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Rust 全局声明体系重构:RFC 0246 对 `const` 与 `static` 的划分及其在现代 Rust 中的演进
文档【免费下载链接】rfcsRFCs for changes to Rust项目地址https://gitcode.com/gh_mirrors/rf/rfcs点击查看免费下载本文以 Rust RFC 仓库中的 text/0246-const-vs-static.md 为绝对主线系统梳理 2014 年该 RFC 提出将全局声明拆分为const常量与static静态变量两大类的完整设计从设计动机、语法与语义规则、全局项相互引用约束、模式匹配限制到它对后续const fn、static生命周期默认值、rvalue 提升等机制的影响。读完本文你将能准确回答什么时候该用const、什么时候该用static、二者为何不可互相替代并理解现代 Rust 中static的Sync约束、只读内存放置与static mut的 unsafe 语义究竟从何而来。背景为什么一个static关键字不够用在 RFC 0246 提出之前2014 年 8 月Rust 只有唯一的关键字static来声明全局变量。默认情况下全局变量不拥有显著地址significant address可以被内联进程序若想让它拥有显著地址需要显式标注#[inline(never)]可变全局则使用static mut而对它的所有访问都被视为 unsafe。此外为了让static可以放进只读内存其类型内部不允许出现UnsafeCell即不允许内部可变性。该 RFC 指出这套单一static设计存在若干具体问题无法安全地构建全局计数器或锁这类需求必须借助static mut而访问它属于非法unsafe操作。#[inline(never)]的语义不直观用是否内联来间接表达是否有显著地址对使用者极不友好。无法声明泛型常量单一static机制无法表达带类型参数的常量。static与let的类比对用户造成误导二者表面相似行为却截然不同——static大多声明的是可内联、无固定地址的值而非变量static不能有内部可变性而let可以static因可出现在模式中而禁止被遮蔽let则相反。语言中其他看似全局数据的构造如无参枚举变体、无字段结构体实际上是无地址的值被归类为 rvalue与static的行为并不一致。这些痛点共同指向一个结论需要一个更清晰的类型系统划分来承载常量值与全局变量两种截然不同的语义。核心提案把全局声明拆成两类RFC 0246 的 Summary 给出了整篇设计的总纲将全局声明划分为两大类别——const常量声明的是常量值它表示一个值value而不是一个内存地址。这是绝大多数场景下开发者会优先选择的机制几乎可以取代当时所有对static的常规使用。static静态变量声明的是全局变量它表示一个内存地址memory address。这类需求很少见主要使用场景是全局锁、全局原子计数器以及与遗留 C 库交互。这一划分确立了此后 Rust 全局声明体系的基本骨架也是现代 Rust 中const与static语义差异的源头。常量const声明的是值RFC 重新引入const声明语法为const name: type value;规则要点如下可在任意作用域声明且不能被遮蔽cannot be shadowed。常量被视为 rvalue对常量取地址时实际是在局部栈上创建一个位置——常量按定义没有显著地址。常量被设计为与无参枚举变体nullary enum variants行为完全一致它们是真正的值不占据固定的内存位置。这一常量即值、无地址的定位为编译器的优化敞开了大门常量值可以被直接内联进程序编译器也可以按需自由复制它们而不必担心别名问题。正如 Motivation 中所说如果常量被内联进下游 crate那么一旦该常量发生变化下游 crate 就必须重新编译——这是内联语义的固有代价。可能的扩展泛型常量RFC 指出允许常量携带泛型参数是完全合理的。例如struct WrappedOptionT { value: OptionT } const NONET WrappedOption { value: None }这个扩展对static而言毫无意义——静态变量表示一个内存位置其类型必须是具体的concrete type。泛型常量这一设想后来以泛型常量表达式等形式在语言中长期演进相关讨论散见于 text/2000-const-generics.md 等 RFC 中而static 必须类型具体这一约束则始终成立。可能的扩展常量函数const fn的雏形RFC 0246 还设想了一种常量函数constant functions用来解决初始化逻辑封装的问题。为避免界定哪些代码可以在常量函数中执行的困难可以将其从语法上限定为单个可在编译期展开的常量表达式禁止递归。原文档给出的示例是struct LockedDataT:Send { lock: Lock, value: T } const LOCKEDT:Send(t: T) - LockedDataT { LockedData { lock: INIT_LOCK, value: t } }RFC 认为这能让我们把UnsafeCell的value字段设为私有因为它提供了在常量上下文中初始化含锁/原子类型的安全途径。这个可能扩展后来被 text/0911-const-fn.md 正式落实2015 年 2 月的 RFC 0911 允许将自由函数与固有方法标记为const使其能在常量上下文中以常量实参被调用从而解决UnsafeCell字段被迫公开的稳定化与安全性隐患例如AtomicUsize、CellT等在常量上下文中无法初始化的问题。RFC 0911 的详细设计中明确列出了常量值表达式的允许集合原始字面量、ADT、基础类型上的运算、类型转换、字段访问、无捕获闭包、引用与块表达式等并排除了副作用与一般 CTFE 机制。从仓库中的文档脉络可以清晰看到RFC 0246 提出常量函数设想 → RFC 0911 将其落地为const fn→ 如今const fn已成为现代 Rust 的核心特性被AtomicBool::new、Cell::new等大量标准库 API 使用text/0911-const-fn.md 中的示例即包含static FLAG: AtomicBool AtomicBool::new(true)等现代写法。静态变量static声明的是内存位置RFC 将static声明重新定位为只声明静态变量规则如下静态变量总是拥有唯一的地址single address。可以可选地声明为mutstatic mut。其生命周期恒为static。不允许从 static 中 move 值。对 static 的访问会产生真实的内存读写其值不会被内联但见文末未决问题。进一步的安全性规则是本 RFC 对现代 Rust 影响最深的部分非mut的 static其类型必须满足Syncbound。对该 static 的一切访问读取变量、取其地址都被视为安全操作。如果 static 的类型内部不包含UnsafeCell编译器可将其放入只读内存否则必须放在可变内存中。mutstatic 可以是任意类型但所有访问均被视为 unsafe且不允许放入只读内存。这条非 mut static 必须Sync、内部无UnsafeCell才可进只读内存的规则直接呼应了 Motivation 中我们希望把大型常量放进只读内存但这要求数据必须真正不可变否则将导致段错误的诉求。它也与 text/0458-send-improvements.md 中在安全代码中所有 static 项都要求Syncbound的描述相互印证——Sync是T可跨线程共享的前提而static恰恰是对static T的最直接利用。全局项之间的相互引用规则RFC 0246 用一整节细致规定了const与static互相引用时的合法性与编译器行为这是最容易写错、也最值得精读的部分。原文档以SomeStruct为例逐一对四种组合做了规定。const const允许但存在内部可变性限制可以创建一个const或static通过地址引用另一个const或staticstruct SomeStruct { x: uint } const FOO: SomeStruct SomeStruct { x: 1 }; const BAR: static SomeStruct FOO;常量通常被内联进引用它的栈帧但在 static 上下文中没有栈帧。此时编译器会将其**改写reinterpret**为struct SomeStruct { x: uint } const FOO: SomeStruct SomeStruct { x: 1 }; const BAR: static SomeStruct { static TMP: SomeStruct FOO; TMP };即编译器引入一个static以便给该常量一个真正具有static生命周期的指针。由于这一改写编译器将禁止SomeStruct内部包含UnsafeCell内部可变性。一般性结论若常量 B 内部含有UnsafeCell则常量 A 不能引用 B 的地址。const static非法常量不允许引用另一个 static。原因在于常量表示常量值而 static 表示内存位置在二者的定义下这种引用难以调和。这正体现了值语义与位置语义的根本冲突。static const允许但只有 Sync 限制如果static引用某个const的地址会发生类似的改写但没有内部可变性限制——只有Sync限制。也就是说被引用的常量类型只需满足Sync即可被静态变量以地址方式引用。static static按值引用被禁止静态变量禁止按值引用另一个 static所有引用都必须是借用borrow而且只允许显式取另一个 static 的地址字段/元素的内部借用interior borrows以及数组元素访问均被禁止。RFC 解释了为何禁止按值引用如果允许按值引用则被引用的 static 只能落入两种情形——它是初始化器模式initializer pattern但这正是const的用途两个值需要保持同步而这在当时技术上不可行。既然两种情形都无法合理成立编译器干脆禁止任何 static 按值引用其他 static。模式匹配中的使用限制在原设计中static允许出现在模式匹配pattern matching中。RFC 0246 规定引入const之后static将被禁止出现在 match 模式中只有const可以出现。这一决定把哪些全局项可作为模式的问题锚定在值语义上——模式匹配本质上是对值的结构化比对与内存位置无关。这一规则在后来的 text/1445-restrict-constants-in-patterns.md 中得到深化RFC 1445 引入了#[structural_match]属性与#[derive(Eq)]自动标记机制将可出现在模式中的常量限定为内置类型或派生了Eq的结构化类型以消解语义相等与结构化相等之间的分歧而更晚的 text/3535-constants-in-patterns.md 则以常量即手写模式的语法糖为立场进一步明确了PartialEq派生、浮点 NaN、指针类型的结构化相等判定规则。可以说RFC 0246 的只有 const 能进模式这一定位是整个常量模式体系后续十余年演进的起点。后续演进RFC 0246 的设计如何在现代 Rust 中落地RFC 0246 提出的不少设想在后续 RFC 中被正式实现和扩展。追踪这些关联文档可以完整还原一条从设计到落地的演进链。static生命周期默认值RFC 1623text/1623-static.md2016 年指出当时在static与const声明中书写引用必须显式写出static ..既繁琐又造成严重的右移漂移。该 RFC 将 RFC 0599 为 trait object 设定的默认值规则推广到 static/const 声明未显式给出生命周期时编译器在全部引用生命周期包括通过泛型替换获得的引用生命周期中默认static。例如下面的类型声明可以从static my_awesome_tables: static [static HashMapCowstatic, str, u32] ..简化为static my_awesome_table: [HashMapCowstr, u32] ..RFC 1623 同时明确该默认值不覆盖函数签名中的生命周期省略规则二者协同工作例如static foo: fn(u32) - u32仍是fora fn(a u32) - a u32显式写出生命周期仍然合法因此该改动完全向后兼容。const fn的正式落地RFC 0911如前所述text/0911-const-fn.md 将 RFC 0246 的常量函数设想落地。除语法规则外RFC 0911 还规定了参数只允许简单的按值绑定如x: T、函数体按const块检查、trait 方法不能为const、const fn也可以是unsafe用于构造需要维护不变量的类型如非空指针等细则并明确不引入一般性 CTFE 机制。rvalue 提升常量进入静态内存RFC 1414 / RFC 3027RFC 0246 设想把常量放进只读内存的优化路径其运行时形态在 text/1414-rvalue_static_promotion.md 中体现为rvalue 提升promotion把 constexpr 右值提升为静态内存中的值从而可以直接构造static引用例如let x: static u32 42。其约束是被提升的 constexpr 不得包含UnsafeCell { ... }构造、不得调用返回含UnsafeCell类型的 const fn——这正是 RFC 0246常量内部不得含UnsafeCell才能进只读内存思想的直接延续。而 text/3027-infallible-promotion.md2020 年则进一步收紧了该机制将隐式提升限制在求值不会失败的表达式中并澄清了术语——让引用获得static生命周期称为lifetime extension而把代码片段抽取为常量的底层机制称为promotion。后者还被用于非Copy数组重复表达式、#[rustc_args_required_const]与asm!的 const 操作数等场景。常量求值上下文的精确定义RFC 1229text/1229-compile-time-asserts.md 从编译器视角精确列举了恰好五处要求表达式为常量的上下文const/static项的初始化器、数组大小[T; EXPR]、重复表达式长度[VAL; LEN_EXPR]、C 风格枚举判别值、以及模式。可以看到这份清单中const/static 初始化器与模式正是 RFC 0246 所定义的核心使用场景二者一脉相承。现代static的典型形态Sync与惰性初始化RFC 0246 说static的主要使用场景是全局锁、全局原子计数器这一判断在标准库层面得到验证。在 text/2788-standard-lazy-types.md 中可以看到现代 Rust 对惰性初始化全局值的标准写法use std::sync::Lazy; // BACKTRACE 实现了 DerefTarget OptionString首次访问时初始化 static BACKTRACE: LazyOptionString Lazy::new(|| { std::env::var(RUST_BACKTRACE).ok() });该 RFC 明确指出惰性值本质上是内部可变性interior mutability的一种形态其安全性的关键前提是单次赋值single assignment——即OnceCell模式。这与 RFC 0246 对static 类型内部出现UnsafeCell时必须置于可变内存、且依赖同步原语保证安全的原始设计相互呼应。同时text/0458-send-improvements.md 关于T在T: Sync时才是Send的规则也构成了static 必须Sync才能被安全共享这一约束的理论基础。设计权衡与替代方案RFC 0246 在 Drawbacks 中坦率承认该设计为全局数据引入了两个关键字而全局数据本身是边缘特性看起来有些用力过猛。不过作者也指出大多数 Rust 程序员只需要掌握const一个关键字——static变量会很少被用到。备选方案让static mut的Sync类型访问安全化RFC 列举了当时正在考虑的另一套设计保持当时单一static的现状但当static mut的类型满足Sync时将其访问视为安全。该方案即 RFC 177 讨论的内容text/0246-const-vs-static.md 的 Alternatives 一节对此有完整描述。与两个关键字方案相比它不引入新关键字但也没有解决没有显著地址的常量值这一核心语义问题。混合策略分阶段实施考虑到时间窗口Rust 1.0 排期紧张RFC 还提出了一种混合路线短期内维持当时的static规则或采用Sync的static mut访问安全变体待 Rust 1.0 发布后再引入const声明。从仓库后续文档RFC 0911 等看const与const fn的完整落地确实是在 1.0 之后的演进中逐步完成的。遗留的未决问题RFC 0246 在 Unresolved questions 中留下了四个问题它们在一定程度上预示了后续多年围绕全局声明体系的讨论方向编译器是否可以对深度不可变的static变量值进行内联从而强制下游重新编译——这一问题与内联带来的重编译成本权衡直接相关后来与 text/3027-infallible-promotion.md 讨论的隐式提升边界问题交织在一起。是否允许类型不满足Sync的static但将其访问标记为 unsafe是否允许类型不满足Sync、但初始化值实际上不含内部可变性的static例如初始值为None的OptionUnsafeCelluint理论上安全设想的扩展泛型常量、常量函数实现难度如何如果容易实现则宜早做困难则可延后。其中第 3 个问题与Sync是类型层面的静态约束、而内部可变性是否真实存在取决于值之间的张力在后来的struct派生与Sync自动实现讨论中反复出现第 4 个问题则直接导向了 RFC 0911 的const fn落地。实践指南如何选择const还是static综合 RFC 0246 及其后续演进可以提炼出现代 Rust 中选择全局声明的决策要点声明纯值、参与编译期计算、需要内联或进入模式匹配→ 使用const。常量无显著地址、被视为 rvalue、可被内联与复制且是唯一能合法出现在模式中的全局项。声明真正的全局内存位置、生命周期为static、需要被取地址共享→ 使用static。此时类型必须满足Sync若类型内部不含UnsafeCell编译器可能将其置于只读内存。需要全局的可变状态如原子计数器、锁、惰性初始化容器→ 在现代 Rust 中优先选择基于内部可变性的安全容器AtomicUsize、Mutex、OnceCell/Lazy而非static mut。static mut的所有访问都是 unsafe且不能放入只读内存应作为最后手段。需要static引用但不想声明全局项→ 依赖 rvalue 提升机制直接写expr获取static引用受 text/1414-rvalue_static_promotion.md 与 text/3027-infallible-promotion.md 的规则约束。这套决策框架的每一条规则都能在 RFC 0246 的原始文本及其后继 RFC 中找到依据——这正是该 RFC 作为 Rust 全局声明体系原点性设计文档的价值所在。结语RFC 02462014 年 8 月用值 vs 内存地址这一清晰的概念划分为 Rust 的全局声明体系奠定了语义地基const承载可内联、无地址、可进只读内存的常量值static承载static生命周期、Sync约束下的全局内存位置而static mut则被边缘化为全 unsafe 的可变全局。此后十余年间RFC 1623 的static默认生命周期、RFC 0911 的const fn、RFC 1445 与 RFC 3535 的常量模式规则、RFC 1414 与 RFC 3027 的 rvalue 提升无不沿着这条主线演进。对于任何希望深入理解 Rust 全局声明语义、或需要做出用 const 还是 static决策的开发者这份 RFC 及其在仓库中的后继文档都是最权威的一手资料。赞分享文档【免费下载链接】rfcsRFCs for changes to Rust项目地址https://gitcode.com/gh_mirrors/rf/rfcs点击查看免费下载相关推荐深入解读 Rust RFC 0071在 static 初始化中使用 const 块表达式深入解读 Rust RFC 0071在 static 初始化中使用 const 块表达式 导读 本文以 Rust RFC 仓库中的 text/0071 con文档Rust By Example 详解const 与 static 常量——从声明语法到 static 生命周期与 unsafe 边界Rust By Example 详解const 与 static 常量——从声明语法到 static 生命周期与 unsafe 边界 本篇技术指南以 Rus文档教程Rust while let 循环模式匹配全解析从 RFC 214 到现代 Rust 的演进之路Rust while let 循环模式匹配全解析从 RFC 214 到现代 Rust 的演进之路 while let PAT EXPR { BODY }文档上一篇Tock 内核周会纪要解析2022-06-10VolatileCell 许可、Digest/HMAC HIL 缓冲区设计演进与 Ti50 调研下一篇探索Peeky可视化界面如何利用UI提升测试体验与效率创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED

相关推荐

嵌入式GUI是库还是系统?FlyThings OS三层架构解析

嵌入式GUI是库还是系统?FlyThings OS三层架构解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/15 2:12:46
webgems 贡献指南:从 Issue 反馈、PR 提交流程到资源条目 JSON Schema 全解析

webgems 贡献指南:从 Issue 反馈、PR 提交流程到资源条目 JSON Schema 全解析

开发工具 【免费下载链接】webgems A curated list of resources for devs and designers. Join me on devcord.com if you are up for a chit chat :) 项目地址: https://gitcode.com/gh_mirrors/we/webgems 点击查看 免费下载 webgems.io 是一个面向开发者与设计…

📅 2026/10/15 2:12:46
LAMMPS `dihedral_style cosine/squared/restricted` 完全指南:MARTINI 力场受限余弦二面角势的实现与使用

LAMMPS `dihedral_style cosine/squared/restricted` 完全指南:MARTINI 力场受限余弦二面角势的实现与使用

科研科学计算高性能计算 【免费下载链接】lammps Public development project of the LAMMPS MD software package 项目地址: https://gitcode.com/gh_mirrors/la/lammps 点击查看 免费下载 cosine/squared/restricted 是 LAMMPS 中为粗粒化 MARTINI 力场设计的二…

📅 2026/10/15 2:12:45
MORE NEWS

更多资讯

📰

APQP平台化引擎:汽车电子研发管理如何告别流程灰区

做了十几年汽车电子研发的项目管理,反复验证出来的一个道理是:绝大多数项目延期,不是死在技术创新上,而是耗在流程的“灰区”里。APQP这套先期产品质量策划方法论,做研发的工程师几乎都背过五大阶段,但真到…

📰

华为题:编辑距离

给定两个字符串 word1 和 word2,请返回将 word1 转换成 word2 所使用的最少操作数。你可以对一个单词进行如下三种操作:插入一个字符删除一个字符替换一个字符输入描述第一行输入字符串 word1。第二行输入字符串 word2。输出描述输出最少操作数。示例 1输…

📰

螺旋矩阵II:用循环不变量与左闭右开区间搞定边界

最近在重新过代码随想录的数组专题,刷到 LC 59 螺旋矩阵 II 的时候,我停下来认真想了一会儿。说实话这题标着中等难度,但第一次动手写的人,十个里有八个会在边界条件上翻车。题目要求非常直白:给你一个正整数 n&#x…

📰

Spring Boot宠物医院管理系统:从数据库设计到部署实战解析

1. 项目概述1.1 这是什么项目,解决什么问题一份宠物医院管理系统的毕业设计源码,技术栈锁定在 Spring Boot。做过毕设的都知道,管理系统类题目是整个毕业设计题库里最稳的选择——需求明确、模块清晰、工作量可控,答辩的时候也容易…

📰

从曲面细分到计算着色器:我的 Vulkan 实验记录

一、曲面细分(Tessellation)做了什么给鸭子模型加了一条细分管线。细分是在渲染管线里插两个新阶段:细分控制着色器(TCS,tessellation control)和细分求值着色器(TES,tessellation e…

📰

自由出入宇宙空间:是工程学奖励还是本体论属性?

“自由出入宇宙各空间”,这句话在主流叙事里几乎自动绑定了一套画面:巨大的推进器、弯曲的时空、层层叠叠的能量井,以及一代代人前赴后继的工程攀登。但标题提出了另一个更狠的猜想——这种自由,或许根本不是一个“工程学奖励”&a…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬