资讯动态

Rust RFC 809 解读:`box` 重载语法与 `in` 放置表达式(placement-in)的设计与撤回始末

发布时间:2026/10/15 2:19:10 来源:尧图企业网站定制
文档【免费下载链接】rfcsRFCs for changes to Rust项目地址https://gitcode.com/gh_mirrors/rf/rfcs点击查看免费下载导读本文基于 RFC 仓库中的 text/0809-box-and-in-for-stdlib.md 展开系统梳理 Rust 在 1.0 前后围绕box关键字重载与placement-new放置式分配的一次重要语言设计尝试。RFC 809 提出将内建的box expr改为按目标类型分派的重载操作符并引入in place-expr { block }语法试图让RcT、ArcT、VecT等类型也获得直接将求值结果写入最终存储、避免中间拷贝的能力。阅读本文后你将理解这套操作符背后的 trait 协议Place/Placer/InPlace/Boxed/BoxPlace、两条 feature gate 的边界、以及该提案最终被撤回并改走-语法RFC 1228的历史脉络。RFC 809 的定位与最终状态RFC 809 的元信息位于文件头部Feature Name 为box_syntax与placement_in_syntax起始日期为 2015-02-04。该 RFC曾被批准但后来被撤回withdrawn文档开篇明确写道This RFC was previously approved, but laterwithdrawn.撤回细节可参阅对应 summary comment跟踪于 rust-lang/rust#27779。理解这一状态很重要本文讲述的是一套曾经被采纳、最终未能稳定下来的语言设计但它直接影响了后续的语法决策见 RFC 1228 的PLACE_EXPR - VALUE_EXPR方案其附录中的 trait 协议与脱糖desugaring思路至今仍被 RFC 1211 MIR 设计 等文档引用是研究 Rustbox语法演进的关键历史材料。核心提案概览三项改动RFC 809 的 Summary 提出了三项核心改动改写 placement-new 语法将box (place-expr) expr改为in place-expr { block }。重载box expr使box expr成为一个重载操作符根据期望的返回类型选择实现而不再与BoxT强绑定。在core::ops中引入不稳定 trait为两个操作符提供底层协议支持libstd 提供实现但 trait 本身保持 unstable以便 1.0 之后语言设计者可以自由修订协议。同时设置两个独立的 feature gateplacement-in语法feature 名placement_in_syntax重载后的box expr沿用原有 feature 名box_syntax。文档特别解释了block的含义——它表示一个块表达式的内部block :: [ stmt ; | item ] * [ expr ]这与参考手册中block非终结符的用法一致块内可以包含任意数量的语句/条目最后可选一个表达式作为块的值。动机两个目标与一个折中目标一类 C placement new 的操作Rust 希望支持与 C placement new 类似的操作在一个**预先指定好的位置place**上构造值而非先构造再移动。该需求在此前的 [Placement Box RFC PR 470] 讨论中已有铺垫。典型场景是容器尾插——Vec::push先将元素构造出来再拷贝/移动进缓冲区而 placement 思路可以直接在缓冲区尾部已分配但未初始化的内存上构造。目标二重载box让RcT/ArcT受益RcT与ArcT这类引用计数类型在构造时如果值很大会经历先构造临时值、再拷贝进堆存储的中间拷贝。通过重载box expr可以让表达式的结果直接安装进RcT或ArcT的底层存储从而规避中间拷贝。讨论中浮现的两点共识在 PR 470 的讨论过程中两点认识逐渐清晰in关键字的多处语法优于box (place-expr) expr。旧语法在需要给expr加括号时不得不写出box () (expr)或box (alloc::HEAP) (expr)这种别扭的形式而in place-expr expr遇到解析错误时也能给出更清晰的诊断信息。过早承诺任何具体协议都不明智。PR 470 提出的协议被认为过于繁琐baroque尤其是不支持 DST且未来的语言演进可能让该协议大幅简化。1.0 的中间路线因此 RFC 提出一个面向 1.0 的折中方案支持想要的语法但不为终端用户提供稳定的操作符实现支持。用户可以稳定使用的方式只有与 stdlib 提供的类型如BoxT配套使用其余类型若要接入协议只能走 nightly 的 feature gate。详细设计语法、trait 协议与解析细节将box expr改为重载操作符RFC 提议把box expr从与BoxT紧密耦合的内建语法改成依据期望返回类型决定创建何种值的重载操作符。例如若RcT实现了对应的操作符 traitlet x: Rc_ box format!(Hello);这将成为合法写法用来构造RcString且对大型T而言比调用Rc::new更高效即便不看效率这种写法在可读性上也更简洁。这带来一个重要的兼容性影响终端代码不能再假设box expr一定产生BoxT。已有代码需要二选一加上类型标注例如let x: Box_ box expr;或者改用Box::new(expr)替代box expr。为in place-expr { block }增加解析支持RFC 提议在解析器中新增对in place-expr { block }的支持作为 placement 操作符的基础从解析器中移除box (place-expr) expr的支持让in place-expr { block }成为重载操作符依据place-expr的类型决定执行哪段放置代码。一个语法细节当place-expr只是一个标识符时place-expr { block }不能被解析成结构体字面量。解决方案与if表达式等场景一致限制place-expr不能包含结构体字面量。这一约束来自 RFC 92struct 文法——该 RFC 主张为表达式后跟块的语法定义一个排除结构体字面量的子类避免通过前瞻解析:来识别结构体从而让文法更健壮。稳定的实现范围RFC 的边界划定非常明确box expr操作符唯一被提议稳定化的实现是BoxT其他类型是否接入box属于库设计问题需走惯例与库稳定化流程。in place-expr { block }操作符不提议任何稳定实现。一个显而易见的候选是Vec::emplace_back方法但同样属于库设计范畴超出本 RFC 范围。其他类型如何接入的示例实现见附录 A。两个独立的 feature gate两条语法分别置于独立 feature 标识下便于单独解禁其中一条而不影响另一条。理由是Rust 对非重载box expr已有大量实践经验但对这里描述的 placement-in几乎没有任何实战经验因此需要保留单独解禁的灵活性。Panic 安全要求无论最终采用哪套协议操作符都必须正确应对 panicbox expr中expr求值若 panic必须清理中间状态in place-expr { block }同理。这意味着放置位置place的实现需要一个析构路径在值写入前发生 panic 时回收已分配但未初始化的存储。附录 A 解读一套可运行的操作符协议RFC 附录 A 给出了今天就能跑通的示例协议——用宏in_!模拟 placement-in、用宏box_!模拟重载box并定义了一组应放入core::ops的 trait。先看目标效果fn main() { use std::rc::Rc; let mut v vec![1,2]; in v.emplace_back() { 3 }; // 返回类型为 () println!(v: {:?}, v); // prints [1,2,3] let b4: Boxi32 box 4; println!(b4: {}, b4); let b5: Rci32 box 5; println!(b5: {}, b5); let b6 in HEAP { 6 }; // 返回类型 Boxi32 println!(b6: {}, b6); }注意这里in v.emplace_back() { 3 }的返回类型是()纯副作用地向v尾插in HEAP { 6 }的返回类型则是Boxi32——同一语法、两种返回类型正是由 place 决定行为的体现。宏层面的脱糖RFC 提供了两个宏来演示脱糖逻辑macro_rules! in_ { (($placer:expr) $value:expr) { { let p $placer; let mut place ::protocol::Placer::make_place(p); let raw_place ::protocol::Place::pointer(mut place); let value $value; unsafe { ::std::ptr::write(raw_place, value); ::protocol::InPlace::finalize(place) } } } } macro_rules! box_ { ($value:expr) { { let mut place ::protocol::BoxPlace::make_place(); let raw_place ::protocol::Place::pointer(mut place); let value $value; unsafe { ::std::ptr::write(raw_place, value); ::protocol::Boxed::finalize(place) } } } }两段脱糖非常相似但有一个关键差异placement-in的InPlace::finalize(place)依据place参数即place-expr推导出的类型重载而重载box的Boxed::finalize(place)依据期望返回类型重载。也就是说两个脱糖中调用哪个 finalize的信息来源不同。五个 trait 的职责划分附录 A 的protocol模块定义了五个 trait可视为core::ops候选协议的完整骨架PlaceData: ?Sized—— 未初始化存储的抽象pub trait PlaceData: ?Sized { fn pointer(mut self) - *mut Data; }Place可以理解为对 Rust 目前无法直接表达的uninit引用的特殊表示它指向未初始化的存储。客户端负责两步先通过pointer拿到地址并初始化载荷必须用ptr::write因为数据一般未初始化再通过合适的finalize将代理转换为最终拥有者。若求值EXPR失败Place实现的析构函数负责清理中间状态如释放 box 存储、弹出栈等。PlacerData: ?Sized—— placement-in的入口pub trait PlacerData: ?Sized { type Place: InPlaceData; fn make_place(self) - Self::Place; }in PLACE { BLOCK }的脱糖为let p PLACE; let mut place Placer::make_place(p); let raw_place Place::pointer(mut place); let value { BLOCK }; unsafe { std::ptr::write(raw_place, value); InPlace::finalize(place) }整个表达式的最终类型由PLACE的类型推导若PLACE类型为P则最终类型为P::Place::Owner。实现该 trait 的值通常是瞬态中间值如Vec::emplace_back的返回值或Copy类型因为make_place按值接收self。InPlaceData: ?Sized——Place针对 placement-in的特化pub trait InPlaceData: ?Sized: PlaceData { type Owner; unsafe fn finalize(self) - Self::Owner; }Owner是in PLACE { BLOCK }最终值的类型。当in仅用于对既有数据结构产生副作用如Vec::emplace_back时Owner可以不带任何信息——例如直接是()。finalize把自身转换为最终值将剩余的解分配/清理责任移交给返回的Owner实例并遗忘自身。Boxed—— 重载box EXPR的核心 traitpub trait Boxed { type Data; type Place; unsafe fn finalize(filled: Self::Place) - Self; }box EXPR的脱糖为let mut place BoxPlace::make_place(); let raw_place Place::pointer(mut place); let value $value; unsafe { ::std::ptr::write(raw_place, value); Boxed::finalize(place) }box EXPR的类型来自周围上下文结果类型T决定使用哪个Boxed实现T as Boxed又决定使用哪个BoxPlace实现即T as Boxed::Place as BoxPlace。Data字段在当时的编译器下无法表达约束因此未被使用。BoxPlaceData: ?Sized——Place针对box EXPR的特化pub trait BoxPlaceData: ?Sized : PlaceData { fn make_place() - Self; }与Placer::make_place(self)不同BoxPlace::make_place()不接收参数创建的是全局全新的放置位置。三个示例实现BoxT支持重载box。RFC 明确强调这是演示性质的实现BoxPlaceT内部用一个OptionBoxT作fake_box通过mem::zeroed()伪造已初始化的T最终在finalize中用mem::swap取出真正的BoxT——这比真实实现低效得多但足以证明重载是可行的。同一实现也顺手为HEAP提供了in HEAP { block }支持impla, T proto::PlacerT for HEAP作者注明这不必进入稳定 libstd。RcT支持重载box。RcPlaceT同样用OptionRcT作占位pointer通过rc::get_mut拿到唯一引用后转换为裸指针。其意义在于证明只要实现了BoxedBoxPlacelet b5: Rci32 box 5;就能合法化。Vec::emplace_back演示 placement-in。通过自定义 traitEmplaceBackT为VecT增加方法pub trait EmplaceBackT { fn emplace_back(mut self) - VecPlacerT; }VecPlacer持有a mut VecTmake_place得到VecPlace。关键在VecPlace::pointerimpla, T proto::PlaceT for VecPlacea, T { fn pointer(mut self) - *mut T { unsafe { let idx self.v.len(); self.v.push(mem::zeroed()); mut self.v[idx] } } }它先记录当前长度push一个零值占位以扩展容量然后返回新槽位的裸指针——之后in_!宏会用ptr::write把真实值写入该地址。InPlace::finalize的Owner为()仅mem::forget(self)而Drop实现用mem::forget(self.v.pop())在值尚未写入时panic 路径把占位元素弹出保证Vec长度一致性。这是一套完整的 panic 安全尾插机制。最终main用宏验证全部场景fn main() { use std::rc::Rc; use impl_in_for_vec_emplace_back::EmplaceBack; let mut v vec![1,2]; in_!( (v.emplace_back()) 3 ); println!(v: {:?}, v); let b4: Boxi32 box_!( 4 ); println!(b4: {}, b4); let b5: Rci32 box_!( 5 ); println!(b5: {}, b5); let b6 in_!( (HEAP) 6 ); // 返回类型 Boxi32 println!(b6: {}, b6); }这段代码配合顶部的#![feature(unsafe_destructor)]、#![feature(alloc)]在当时的 Rust 上可以完整运行是整份 RFC 中最有实战参考价值的部分。已知缺陷脱糖与类型推断/强转的交互RFC 承认当前编译器实现存在两处弱点box expr as BoxTrait的推断问题在box expr as BoxTrait中box expr本应推导为某个Box_但当时的编译器做不到作者 pnkfelix 判断这是编译器本身的缺陷[Rust PR 22012]。与 trait 对象隐式强转的组合问题当box expr出现在类似下面的上下文中fn foo(BoxSomeTrait) { ... } foo(box some_expr());类型推断系统会尝试把BoxSomeTrait与::protocol::Boxed::finalize(place)的返回类型统一同样可能失败。这可能也是编译器缺陷但当时并不明显。附录 B 给出了完整的复现代码用cfg属性切换变体并附带了rustc的报错记录。报错示例展示了两种典型失败模式the trait PlaceF is not implemented for the type BPcore::ops::Fn() [E0277]——期望类型被强行统一为 trait 对象Fn()后Place约束无法满足the trait core::marker::Sized is not implemented for the type [T] [E0277]——Box[T]场景中切片未实现Sized导致BoxPlace::make()的Sized约束失败。附录 B 的两个用例分别来自真实代码fn coerce源自libsyntaxcrate 的fn combine_structurefn duh源自Defaulttrait 对Box[T]的实现。两者本质都是从指向 sized 类型的指针强转为指向 unsized 类型trait 对象 / 切片的指针。当时结论是两例都可以通过**显式类型标注type ascription**绕开例如let b: Box_ box_!( f ); b但fn duh的场景尤其麻烦因为需要让类型中的数组长度与表达式生成的数组长度保持一致这促使作者考虑在类型表达式中扩展_通配符以支持数组长度即[T; _]。备选方案评估RFC 列举了多种替代语法核心约束只有两条不引入文法歧义保持从左到右的求值顺序place 在文本上必须出现在值表达式的左侧。具体备选包括保留box (place-expr) expr除非能证明大量类型同时受益于重载box与 placement-in或box模式语法需要与 placement 共用box关键字否则看不出优势。in (place-expr) expr圆括号版本早期版本曾采用在当前代码库上更易实现但作者不认为有其他好处也许括号比花括号轻量一些。box (in place-expr) expr一个试图安抚反对 placement-in去掉box者的折中写法。什么都不做即连仅供 libstd 使用的不稳定协议都不接受。作者认为这也可行但不理想因为历史上已有用户指出中间拷贝是效率损失来源而box expr与 placement-in的正确使用可以消除中间拷贝。未决问题RFC 明确说明它代表当前计划但在解禁 feature 之前需要妥善解决以下问题类型推断/强转系统能否增强到让重载box与in无缝可用还是说重载必然需要类型标注作者假定当前部分弱点不能归咎于示例脱糖的具体细节。是否要更改in(place) expr/in place { expr }的语法是否根本不需要in能否用未来特性如DerefSet、out等取代是否改进协议本身注意协议被明确排除在本 RFC 之外候选改进包括支持 DST 表达式如动态长度的box [22, ..count]、利用更高级的语言特性等。历史走向从in到-再到撤回理解 RFC 809 的完整意义需要看它后续的演进轨迹这些线索在 RFC 仓库中可以直接交叉验证第一步语法被改写成-。RFC 1228place-left-arrow 记录了反馈链很多人不喜欢in (PLACE_EXPR) VALUE_EXPR与in PLACE_EXPR { BLOCK_EXPR }形式理由包括in单独用作语句显得不自然in容易让人联想到for x in y迭代和elem in set成员测试块操作数让人误以为是控制流结构。RFC 1228 因而提议改用PLACE_EXPR - VALUE_EXPR它利用历史遗留中被保留的-token短小、无歧义、且天然带有赋值的联想。其脱糖语义与box (EXPR) EXPR/in EXPR { BLOCK }完全等价先求 place、再直接求值写入 place、最后返回 finalize 后的值并计划在时机成熟后移除box (PLACE_EXPR) VALUE_EXPR与in PLACE_EXPR { VALUE_BLOCK }两种旧语法让-成为 placement-new 的唯一正道。RFC 1228 还讨论了-的优先级四种可选方案低于赋值、与赋值相同、介于赋值与二元操作符之间、高于一切操作符最终采纳高于赋值但低于其他二元操作符的折中并由后续 [RFC 1319] 调整。第二步整体撤回。两个 RFC 最终都在 rust-lang/rust#27779 的 summary comment 中被撤回。从仓库现状看placement_in_syntax与box_syntax均未成为稳定的语言特性——现代 Rust 使用Box::new、vec![x; n]、Arc::new等显式构造placement-new 至今未落地为稳定语法。第三步box相关语法继续收窄。RFC 469 将box模式移到box_patternsfeature gate 之后当时社区对是否把box模式重命名为deref没有共识且未来box模式可能泛化为可解构任何实现Deref族的类型故需保持 gate 化RFC 2421 中box仍以nightly 上用于 box patterns的形式存在。这些后续决定与 RFC 809 将box从内建特殊语法重构为通用操作符的努力方向一脉相承。旁证MIR 中的浅拷贝 drop。RFC 1211MIR 在讨论 drop 语义时专门用let x box Foo::new();的例子说明若Foo::new()在调用途中 unwinding编译器只需释放已分配的 box 外壳、而不释放尚未初始化的内容shallow drop。该 RFC 明确写道这类内建 box 代码属于遗留机制RFC 809 规定的更通用协议以大致相同的方式工作当其被统一采用后对 shallow drop 与 Box rvalue 的需求将消失——足见 RFC 809 的协议思路在编译器设计中是被认真对待过的。小结与启示RFC 809 是一份技术完整、结局遗憾的设计文档它给出了 placement 操作符的完整 trait 协议、可运行的宏级脱糖与三个类型Box、Rc、Vec::emplace_back的示例实现也诚实地暴露了当时类型推断与强转系统的短板。它最终未能稳定但留下了三份遗产语法探索的收敛路径从box (place) expr→in place { block }→-体现了 Rust 对无歧义、左到右求值、短小直观的语法审美协议设计的范本Place/Placer/InPlace/Boxed/BoxPlace五 trait 的职责切分未初始化存储抽象、按 place 重载 vs 按期望类型重载、panic 清理责任被后续编译器设计反复引用对重载 类型推断边界的一次实测附录 B 的用例与报错记录是研究 Rust 类型推断/强转与重载交互的第一手材料。对研究者而言RFC 809 原文、RFC 1228、RFC 1211 MIR 三份文档相互印证勾勒出 Rustbox语法从内建走向可重载再走向放弃的完整时间线。这份 RFC 提醒我们语言特性即使被批准、被实现也可能因为生态反馈与类型系统的现实约束而被重新审视——而这份审视过程本身恰恰是 RFC 流程最宝贵的产出。赞分享文档【免费下载链接】rfcsRFCs for changes to Rust项目地址https://gitcode.com/gh_mirrors/rf/rfcs点击查看免费下载相关推荐Metabase 表达式 in 函数详解语法、SQL 对照与底层实现Metabase 表达式 in 函数详解语法、SQL 对照与底层实现 in 是 Metabase 自定义表达式Custom Expressions中最常用数据分析数据可视化后端数据库客户端企业应用Comprehensive Rust 函数入门声明语法、隐式返回与表达式求值Comprehensive Rust 函数入门声明语法、隐式返回与表达式求值 导读 本文聚焦 Google 安卓团队开源 Rust 课程Comprehens文档教程rust-gpu 资源绑定语法设计从 SPIR-V 描述符到 Rust 语言接口的 RFC 解读与实现rust gpu 资源绑定语法设计从 SPIR V 描述符到 Rust 语言接口的 RFC 解读与实现 导读 在 GPU 着色器开发中如何把纹理、缓冲、采样图形学编译器游戏开发上一篇从Vue2到Vue3ant-design-vue-pro项目迁移完全指南下一篇Ant Design Vue Pro开发规范团队协作的代码风格指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑