文档【免费下载链接】rfcsRFCs for changes to Rust项目地址https://gitcode.com/gh_mirrors/rf/rfcs点击查看免费下载本文深度解读 Rust RFC 仓库中的 RFC 1228placement-left-arrow。该 RFC 提出用左箭头运算符PLACE_EXPR - VALUE_EXPR作为 placement-new放置式构造的统一语法以取代当时基于in关键字的in PLACE { BLOCK }形式。读完本文你将完整掌握该方案的设计动机、三段式执行语义、脱糖desugaring原理、四种优先级候选方案以及它最终被撤回的历史背景与后续影响。背景placement-new 与「中间拷贝」问题placement-new 的概念源自 C不是先构造一个值再拷贝/移动到容器里而是把值表达式直接「放置」到预先准备好的内存位置place上从而避免不必要的中间拷贝。在 Rust 语境下典型场景包括向Vec尾部追加一个大型结构体时希望直接在该位置初始化元素向竞技场分配器arena申请内存时希望把值直接构造进分配好的槽位并返回对该槽位的引用或句柄构造RcT/ArcT时希望直接把值写入其内嵌存储避免先构造T再拷贝。Rust 社区对这一能力的语法形式经历了多轮演进。最早的形式是box (PLACE_EXPR) VALUE_EXPR随后在 RFC 809 的讨论中转向基于in关键字的写法最终落地为in PLACE_EXPR { BLOCK_EXPR }。而 RFC 1228 正是对这一「第二版」语法的又一次重大修正提案。RFC 1228 提案核心PLACE_EXPR - VALUE_EXPRRFC 1228 的 Summary 非常直白与其继续在in关键字上寻找巧妙语法不如直接采用PLACE_EXPR - VALUE_EXPR这个形式之所以可行是因为-这个 token 早已因历史巧合historical accident被 Rust 保留reserved而这一次巧合「罕见地站在了我们这边」——一个既有的保留 token 可以直接被征用为运算符无需为语法让路。三段式执行语义RFC 1228 明确指出PLACE_EXPR - VALUE_EXPR的静态与动态语义与box (PLACE_EXPR) VALUE_EXPR完全等价仍然是一种按以下三步工作的表达式形式将PLACE_EXPR求值为一个 place放置位置将VALUE_EXPR直接求值并写入这个已构造好的 place返回「定稿后的 place 值」finalized place value。该行为背后的底层协议详见 RFC 809 的文档说明。实战示例竞技场分配RFC 1228 给出了最典型的应用场景——向竞技场分配器申请槽位。竞技场arena需要同时接收「竞技场本身」和一个「值表达式」并返回被分配条目的引用或句柄因此返回值不能是()let ref_1 arena - value_expression; let ref_2 arena - value_expression;对照同一逻辑在 RFC 809 语法下的写法let ref_1 in arena { value_expression }; let ref_2 in arena { value_expression };两者的差异一眼可见左箭头版本更接近普通赋值语句的形态而in arena { ... }看起来像某种控制流结构。动机为什么「左箭头」优于in语法RFC 1228 的动机部分用一句话概括语法a - b简短、可以无歧义地解析并且已经与「赋值」形成了强烈的语义关联。围绕此结论RFC 正文还整理了大量来自 RFC 809 讨论期的社区反馈用以说明in语法没能解决的那些反对意见kennytm 的反对关于in (place) value形式虽然in (place) value在语法上无歧义但单独作为一条语句时看起来非常不自然主要是因为动词没有出现在正确的位置而且单独使用in通常让人联想到迭代for x in y和成员测试elem in set。petrochenkov 的反对强调语法要尽量短小便捷正如 C11 的经验所证明的一旦这种能力可用它就会成为容器插入元素的默认方式因为它绝不比「普通插入」更差而且常常更好。所以它真的应该拥有尽可能短小便捷的语法。p1start 的反对针对in place { stmts }的块状形式我不喜欢in place { stmts }仅仅因为块block这个要求暗示它是一种控制流结构或者暗示块内的所有语句都会以某种方式在给定的place中执行。这将是我们第一个本质上只是一个运算符、却不得不带一个块操作数的语法构造。RFC 1228 的作者pnkfelix认为PLACE_EXPR - VALUE_EXPR语法能够回应上述全部顾虑它是真正的二元运算符形态、没有块、没有in关键字带来的迭代/成员测试联想且天然满足「占位符在左、值在右」的从左到右求值顺序。详细设计解析、脱糖与 feature gateRFC 1228 的 Detailed design 部分对实现路径给出了具体规划。语法与结合性扩展解析器以解析EXPR - EXPR左箭头运算符右结合right-associative优先级高于赋值与复合赋值binop-assignment但低于其他二元运算符这是 2016-04-22 由 [RFC 1319] 修订后的结论详见下文「优先级设计」一节。脱糖方式EXPR - EXPR会被解析成某种 AST 形式并按照与in EXPR { BLOCK }或box (EXPR) EXPR相同的方式脱糖该脱糖实现由 rust-lang/rust 的 PR 27215 落地。feature gate 的处理该解析形式可以单独设置 feature gate但由于 placement-in语法刚随 PR 27215 落地、本身仍处于placement_in_syntaxfeature gate 之下也可以选择直接把这一改动并入既有的placement_in_syntaxgate——尽管这有点反直觉因为新语法中已经不再出现in关键字。值得注意的是RFC 1228 提到该特性已经有一个原型实现作者在place-left-syntax分支上完成了 prototype说明解析与脱糖路径的可行性在当时已得到验证。移除旧语法让-成为「唯一正道」在足够的快照snapshot和时间窗口之后计划移除以下两种旧语法box (PLACE_EXPR) VALUE_EXPRin PLACE_EXPR { VALUE_BLOCK }即PLACE_EXPR - VALUE_EXPR将成为表达 placement-new 的唯一方式one true way。同时需要注意box VALUE_EXPR无 place 参数的重载盒语法将继续保留作为顺带收益(box ())这个表达式将变得无歧义、从而可以合法化——RFC 作者调侃说「毕竟那些『单元的盒子』的语法真的很值得优化」。文档层面的配套动作RFC 1228 还建议作为流程的一部分应修订 RFC 809 的正文让其改用a - b语法——因为很多人即使 RFC 已过时仍会把它当作参考来源如果做不到至少要在 RFC 809 中加上指向本 RFC 的前向引用。语义深挖placement 协议与脱糖示例要真正理解PLACE_EXPR - VALUE_EXPR在底层做什么需要回到 RFC 809 的 Appendix A。它用宏演示了 placement 与重载box的脱糖过程并给出了完整的协议 trait 设计。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) }重载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) }两者唯一的差别在于finalize的重载依据placement 形式依据place 参数其类型源自place-expr选择InPlace::finalize而box形式依据期望的返回类型选择Boxed::finalize。这决定了同一个表达式在两种语境下的类型推导方向不同。协议 trait 一览RFC 809 附录 A 中演示的协议由以下 trait 组成在正式实现中它们属于core::ops且刻意保持不稳定状态以便语言设计者在 1.0 之后仍可修订底层协议/// 表示对未初始化存储的指针 pub trait PlaceData: ?Sized { fn pointer(mut self) - *mut Data; } /// in PLACE { BLOCK } 的入口由 PLACE 的值构造出一个中间 place pub trait PlacerData: ?Sized { type Place: InPlaceData; fn make_place(self) - Self::Place; } /// in PLACE { BLOCK } 的定稿接口 pub trait InPlaceData: ?Sized: PlaceData { type Owner; unsafe fn finalize(self) - Self::Owner; } /// box EXPR 的核心 trait pub trait Boxed { type Data; type Place; unsafe fn finalize(filled: Self::Place) - Self; } /// box EXPR 的 place 特化 pub trait BoxPlaceData: ?Sized: PlaceData { fn make_place() - Self; }几个关键语义点Place可以理解为「假想的uninit引用」的替身——它代表指向未初始化存储的指针初始化时应使用ptr::write而不是直接赋值in PLACE { BLOCK }的整体类型由PLACE的类型推导若PLACE类型为P则整个表达式类型为P::Place::Owner当in PLACE { BLOCK }只用于对既有数据结构做副作用如Vec::emplace_back时Owner可以是单元类型()不携带任何信息协议必须正确应对 panic如果求值VALUE_EXPR时 panicPlace实现的析构函数必须清理所有中间状态如释放盒存储、弹出栈顶元素等。Vec::emplace_back的示例实现要点RFC 809 附录 A 给出了一个完整的Vec::emplace_back演示实现其核心思路是VecPlacer持有a mut VecT通过Placer::make_place生成VecPlaceVecPlace::pointer先记录当前长度、push一个mem::zeroed()占位再返回新槽位的裸指针供ptr::write写入真实值InPlace::finalize返回()并mem::forget(self)把清理责任转移掉同时为VecPlace实现Drop在写入过程中发生 panic 时pop掉占位元素保证Vec内部状态一致。这套协议正是PLACE_EXPR - VALUE_EXPR在底层要对接的同一套机制——左箭头语法只是它的「表面语法」surface syntax之一。优先级设计四种候选方案RFC 1228 花了较大篇幅讨论-运算符的优先级给出了从「低于赋值」到「高于一切运算符」的完整谱系。这是该 RFC 最具设计深度的部分四种方案与各自的解析结果如下。方案 1低于赋值假设()永远不会成为Placer那么x y - z这类语句将因类型检查不通过而迫使程序员写出x (y - z)从而回应「x y - z含义不清」的常见抱怨。但代价是let x 与x 在解析上产生不一致let x (y - z)成立而(x z) - y也成立。方案 2与赋值、复合赋值同级x y - z a - b c d - e - f解析为x (y - (z (a - (b (c (d - (e - f)))))))这是当时编译器中最容易实现的方案。方案 3高于赋值/复合赋值、低于其他二元运算符本 RFC 采用这允许含等号与-的各种表达式被合理且一致地解析。例如x y - z a - b - c解析为x ((y - z) (a - (b - c)))方案 4高于一切运算符适合想强制使用者大部分时间把-两侧都括进括号的场景x - y - z * a解析为(x - (y - z)) * a权衡与备选方案Drawback意图不如关键字清晰RFC 1228 唯一明确的 drawback 来自 nikomataskis 在 RFC 809 讨论中的评论其意图不如使用一个专用关键字来得清晰。但 RFC 作者指出该评论原本是针对「重载运算符」这一假设情形提出的而-本身可以视为一种足够「专用」devoted的独立语法足以平息上述顾虑。备选方案in与-并用如果希望强调这不是普通的赋值可以把in与-组合起来得到let ref_1 in arena - value_expression; let ref_2 in arena - value_expression;此外RFC 809 的 Alternatives 章节还讨论过其他表面语法例如in (place-expr) expr、box (in place-expr) expr等均可作为本方案的横向参照。未决问题与变更记录优先级悬而未决RFC 1228 的 Unresolved questions 只留下一个问题-运算符的优先级到底取多少具体来说让-与同级可能更合理相关论点见 huon 在 RFC 1319 讨论中的评论。最终答案大概率取决于a - b的结果是否会被高频地组合使用、以何种方式组合因此作者决定暂缓决策等社区中出现更多实际用法后再定夺。变更记录2016-04-22由 rust-lang/rfcs#1319 修订调整了-的优先级即上文采用的「高于赋值、低于其他二元运算符」方案。结局与历史意义一份被撤回的 RFC需要特别说明的是RFC 1228 与 RFC 809 目前在仓库中都标注着同一状态曾被批准approved但后来被撤回withdrawn细节见各自头部引用的跟踪 issue 总结评论。这意味着左箭头语法最终没有成为 Rust 的稳定语法——可以推断当时相关的 placement 语法线整体没有走向稳定但该 RFC 的设计价值依然成立它是理解「放置式构造需要什么样的表面语法」的关键历史文献后续的语法类 RFC 还把它当作反面/正面参考。例如 RFC 1331grammar-is-canonical 在动机部分明确点名#1228连同#1219、#1192是「近期合并的、以非正式方式描述语法变更」的 RFC 之一正是这类经验催生了「以src/grammar形式化文法作为规范」的后续改革。从今天的 Rust 回望in PLACE { BLOCK }与PLACE_EXPR - VALUE_EXPR都没有进入稳定语言Vec尾插避免中间拷贝的诉求最终通过vec![...]的宏内联等手段部分满足而 placement 语法本身则长期停留在 RFC 历史的档案中。RFC 1228 的「三段式语义 优先级谱系 协议脱糖」讨论框架依然是任何想在 Rust 中设计二元运算符式内存构造语法的后来者最值得研读的起点。延伸阅读RFC 1228 原文placement-left-arrowRFC 809 原文box-and-in-for-stdlibplacement 协议与脱糖示例、附录 A/BRFC 1331grammar-is-canonical对语法类 RFC 的形式化文法要求点名引用本 RFCRFC 过程说明了解「active / withdrawn」等 RFC 生命周期的含义赞分享文档【免费下载链接】rfcsRFCs for changes to Rust项目地址https://gitcode.com/gh_mirrors/rf/rfcs点击查看免费下载相关推荐Rust RFC 0169 解读use PATH as ID 重绑定语法改革——从 到 as 的语言演进Rust RFC 0169 解读 use PATH as ID 重绑定语法改革——从 到 as 的语言演进 导读 本文深入解读 Rust RFC 仓库中的文档rust-gpu 资源绑定语法设计从 SPIR-V 描述符到 Rust 语言接口的 RFC 解读与实现rust gpu 资源绑定语法设计从 SPIR V 描述符到 Rust 语言接口的 RFC 解读与实现 导读 在 GPU 着色器开发中如何把纹理、缓冲、采样图形学编译器游戏开发Ant Design Popconfirm 位置配置实战12 种 placement 方向与 arrow pointAtCenter 箭头指向Ant Design Popconfirm 位置配置实战12 种 placement 方向与 arrow pointAtCenter 箭头指向 本篇指南以 P前端UI组件设计系统上一篇PlayIntegrityFix终极实战指南3步解决Google Play设备认证问题下一篇3步掌握Sniffles2长读测序结构变异检测的终极解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考