Mojo Origin 重命名解析:ExternalOrigin 与 AnyOrigin 如何演变为 UntrackedOrigin 与 UnsafeAnyOrigin 📅 发布时间:2026/9/12 6:47:40 👁 浏览次数: Mojo Origin 重命名解析ExternalOrigin 与 AnyOrigin 如何演变为 UntrackedOrigin 与 UnsafeAnyOrigin【免费下载链接】mojoThe Modular Platform (includes MAX Mojo)项目地址: https://gitcode.com/GitHub_Trending/mo/mojo本文围绕 Mojo 编译器的一份已接受提案untracked-and-any-origin-renames.md状态 Accepted作者 Nathan Ward2026 年 6 月展开讲清两个 origin 别名为什么要重命名为UntrackedOrigin与UnsafeAnyOrigin、这两种 origin 在生命周期分析与独占性检查中的相反行为以及当前仓库标准库中这些类型的真实定义与调用点帮助读者在编写 Mojo 指针、引用相关代码时正确理解编译器对每个引用的生命周期语义。一、提案概览让名字如实描述与生命周期分析的关系提案的核心内容是一次命名变更目标是让 origin 别名描述它们如何与编译器的生命周期分析交互ExternalOrigin→UntrackedOrigin含其Mut/Immut变体AnyOrigin→UnsafeAnyOrigin含其Mut/Immut变体。两者被区别对待的理由是UntrackedOrigin是一个合法、受支持的工具因此获得一个朴素、描述性的名字UnsafeAnyOrigin是一个临时的编译器逃生舱escape hatch它会让生命周期分析与独占性分析失效——提案明确它永远不会稳定化并计划在将来弃用和移除因此名字本身就要劝退开发者。从当前仓库源码可以印证这次重命名已经落地标准库 origin 模块 中同时保留了旧别名AnyOrigin/MutAnyOrigin/ImmutAnyOrigin与新别名UnsafeAnyOrigin/MutUnsafeAnyOrigin/ImmUnsafeAnyOrigin而 prelude 将新旧名字一并导出编译器侧的 IR 打印代码LITUtils.cpp也已经直接使用MutUntrackedOrigin、MutUnsafeAnyOrigin等新名输出诊断信息说明诊断输出与新命名体系一致。二、背景origin 驱动生命周期分析Mojo 编译器内置一个生命周期检查器lifetime checker它对你的程序进行数据流分析。它利用 origin 回答关于每个引用的两个问题这个引用指向哪个值它还可能与哪些值发生别名alias这个引用是否可写mutable基于这两个答案检查器完成两项工作生命周期扩展lifetime extension让一个值一直存活到从它派生的最后一个引用被使用完毕随后尽快销毁ASAP destruction即立即销毁原则独占性检查exclusivity checking拒绝在同一时间跨函数边界持有指向同一值两个可变引用或一个可变、一个不可变引用的程序。大多数 origin 都绑定到一个具体的值——origin_of(x)、UnsafePointer(tox)推断出的 origin 等——检查器会精确跟踪它们。ExternalOrigin新名UntrackedOrigin与AnyOrigin新名UnsafeAnyOrigin是仅有的两种跳出这种跟踪的 origin而且方向相反UntrackedOrigin是空 origin不别名任何值UnsafeAnyOrigin是全值 originuniversal origin可能别名任何东西。这一设计与更完整的 origin 系统提案origin-design.md一脉相承。三、ExternalOrigin→UntrackedOrigin承诺不别名任何值3.1 有 origin 时编译器自动延后销毁当一个类型或引用携带绑定到某个值的 origin 时编译器会把该值的生命周期延长到引用的最后一次使用fieldwise_init struct Loud(Writable): var s: String def __del__(deinit self): print(t{self}.__del__) def main(): var loud Loud(abc) print(Creating pointer to Loud) # __type_of(ptr) UnsafePointer[Loud, origin_of(loud)] var ptr UnsafePointer(toloud) print(Printing...) print(ptr[]) # loud 在此处销毁在 ptr 最后一次使用之后。因为ptr的类型是UnsafePointer[Loud, origin_of(loud)]——即origin_of(loud)被携带在指针类型的 origin 类型参数里——编译器能把ptr关联回loud不会在ptr的最后一次使用之前销毁loudCreating pointer to Loud Printing... Loud(sabc) Loud(sabc).__del__3.2 换成 UntrackedOrigin编译器看不见这条依赖Untracked origin 则主动退出这套跟踪它承诺该引用不别名任何由编译器管理的值因此生命周期检查器既无东西可跟踪、也无东西可延长。def main(): var loud Loud(abc) print(Creating pointer to Loud) # ptr 的 origin 被 cast 成 MutUntrackedOrigin。 var ptr UnsafePointer(toloud).unsafe_origin_cast[MutUntrackedOrigin]() # loud 在此处销毁——编译器看不到任何指向它的活跃引用。 print(Printing...) print(ptr[]) # -- OH NO ! 读取未初始化内存现在ptr不再携带origin_of(loud)编译器不知道loud的生存期依赖于ptr于是loud被提前销毁而ptr仍指向它Creating pointer to Loud Loud(sabc).__del__ Printing... BOOM ! UB? 也许能打印出值指针类型仍然带有一个 origin但由于它是UntrackedOrigin编译器在生命周期分析中不跟踪它——这正是 Untracked 这个名字的由来。把编译器托管值的 origin 像这样 cast 掉是不安全的因为它可能让ptr悬空dangling。对应的 API 在当前标准库中是Pointer.unsafe_origin_cast见 pointer.mojo其安全说明明确写着修改指针的 origin 本质上是极不安全的……可以考虑在函数层面参数化 origin以避免不必要的 cast。3.3 合法场景对接 Mojo 程序之外的内存这种不跟踪的行为恰恰是处理程序外内存时的理想选择。提案以alloc()为例它返回一个携带UntrackedOrigin的指针因为分配出来的内存块不别名任何 Mojo 拥有的值检查器不应为它延长任何生命周期。仓库源码印证了这一点alloc.mojo 中alloc()等分配函数返回的指针类型即为Pointer[Self.T, MutUntrackedOrigin]例如第 656、671、693 行OwnedPointer的底层存储类型同样是Pointer[Self.T, MutUntrackedOrigin]第 542 行。标准库的origin模块文档字符串也复述了这一点origin/init.mojoalloc()返回的指针携带 untracked origin因为分配出的块不别名任何 Mojo 拥有的值。此外编译器还会在解析阶段主动给出建议DeclResolution.cpp 中有一条诊断 note 提示开发者alternatively, use UntrackedOrigin if ...即在新命名下用UntrackedOrigin表达此引用与编译器管理的值无关。四、AnyOrigin→UnsafeAnyOrigin全值 origin 的三个代价AnyOrigin与UntrackedOrigin正好相反它可能别名任何值这迫使生命周期检查器进入最保守的行为。它会破坏生命周期延长、隐藏诊断信息、并让独占性检查失效。提案将其改名为UnsafeAnyOrigin使每一处使用点读起来都像 unsafe。下面示例使用新名字MutUnsafeAnyOrigin即当前的MutAnyOrigin。4.1 它会延长不相干值的生命周期由于UnsafeAnyOrigin引用可能别名任何活跃值编译器会让作用域内所有其他值在整个UnsafeAnyOrigin存活期间保持活跃——即使指针根本不会指向它们——相当于实际上停摆了 ASAP destructiondef main(): var loud Loud(abc) print(Creating pointer to n) var n 42 var ptr UnsafePointer(ton).as_unsafe_any_origin() # ~~~~~~~~~~~~~~~~~~~~~~~ 将 origin cast 成 UnsafeAnyOrigin print(ptr[]) # loud 本应在创建后立即销毁 # 却被保活到这里。Creating pointer to n 42 Loud(sabc).__del__对比origin 正确时ASAP destruction 正常执行def main(): var _loud Loud(abc) # 没有任何东西引用 loud所以它被立即销毁。 print(Creating pointer to n) var n 42 var ptr UnsafePointer(ton) print(ptr[])Loud(sabc).__del__ Creating pointer to n 424.2 它会隐藏未使用变量警告同一套生命周期延长效应会压制未使用变量诊断。下面unused从未被读取但因为UnsafeAnyOrigin指针可能别名它编译器把它视为活跃而保持沉默def main(): # 没有警告 ——编译器认为 ptr 可能别名 unused。 var unused Im an unused string! var n 42 var ptr UnsafePointer(ton).as_unsafe_any_origin() # ~~~~~~~~~~~~~~~~~~~~~~~ 将 origin cast 成 UnsafeAnyOrigin print(ptr[])给ptr一个具体 origin警告又能正确触发def main(): var unused Im an unused string! var n 42 var ptr UnsafePointer(ton) # origin 推断为 origin_of(n) print(ptr[])warning: assignment to unused was never used; assign to _ instead? var unused Im an unused string! ^4.3 它会让可变独占性检查失效当某个参数的 origin 是UnsafeAnyOrigin时编译器无法证明它别名哪个值因此不会对该参数报独占性违规。下例中两个指向x的可变指针传入了同一个函数但违规未被报告def two_mut_pointers( p1: UnsafePointer[mutTrue, Int, _], p2: UnsafePointer[mutTrue, Int, _], ): pass def main(): var x 42 # 尽管传入了两个指向 x 的可变指针却没有报错。 two_mut_pointers( UnsafePointer(tox).as_unsafe_any_origin(), # ~~~~~~~~~~~~~~~~~~~~~~~ 将 origin cast 成 UnsafeAnyOrigin UnsafePointer(tox), )两个参数都带具体 origin 时编译器能捕获这个别名违规def main(): var x 42 two_mut_pointers( UnsafePointer(tox), UnsafePointer(tox), )error: argument of two_mut_pointers call allows writing a memory location previously writable through another aliased argument two_mut_pointers( ^ note: x memory accessed through reference embedded in value of type UnsafePointer[Int, origin_of(x)] two_mut_pointers( ^4.4 源码层面的佐证标准库为这个操作提供了专用方法as_unsafe_any_originpointer.mojo其安全说明与提案措辞高度一致始终优先保留具体的 origin……cast 成UnsafeAnyOrigin是不安全的操作它会静默延长不相干的生命周期并关闭独占性检查。内部实现是一次pop.pointer.bitcastMLIR 操作即只改变类型标注、不产生任何运行时检查。生命周期检查器侧CheckLifetimes.cpp 中存在专门的AnyOriginAttr处理路径handleAnyOriginUse第 2764 行以及形如extended with AnyOrigin usage extended lifetime of ...的诊断第 4474 行——从源码结构看检查器内部仍沿用AnyOrigin的 IR 属性名#lit.any.origin而面向用户的别名已经换成UnsafeAnyOrigin前缀。在 origin 模块定义中UnsafeAnyOrigin的文档字符串逐条列出了上述三条代价延长不相干生命周期、隐藏未使用变量警告、关闭可变独占性检查并声明这是 Mojo 早期遗留的临时编译器逃生舱不是应当主动使用的能力它永远不会稳定化并计划弃用与移除。Unsafe前缀把每处使用都标记为需要迁出的位置。五、为什么要重命名以及未来的路径提案对AnyOrigin的定位非常明确它不是一个希望开发者去用的能力而是 Mojo 早期为掩盖棘手生命周期边缘案例而引入的临时编译器构造上面三个行为恰恰就是它破坏 origin 系统所应提供保证的方式。因此它永远不会稳定化并计划在未来弃用和移除。提案给出的前进方向是改进 origin 系统使今天人们使用AnyOrigin的那些模式能够以安全的方式表达同时编译器继续跟踪生命周期与独占性。在那之前Unsafe前缀的作用是把每一处残留用法标记为待迁移点而不是一个可采纳的工具。六、新旧命名速查与仓库中的现状旧名新名语义当前仓库位置ExternalOriginUntrackedOrigin空 origin不别名任何值检查器不跟踪origin/init.mojo#L95MutExternalOrigin/ImmutExternalOriginMutUntrackedOrigin/ImmUntrackedOrigin可写 / 只读版本origin/init.mojo#L116AnyOriginUnsafeAnyOrigin全值 origin可能别名任何值origin/init.mojo#L54MutAnyOrigin/ImmutAnyOriginMutUnsafeAnyOrigin/ImmUnsafeAnyOrigin可写 / 只读版本旧别名保留origin/init.mojo#L83几点使用提示以当前仓库内容为准对外部内存用UntrackedOrigin与alloc()/dealloc()生态、C FFI 外部指针交互时MutUntrackedOrigin是正确且受支持的标注无需担心未跟踪意味着有问题见到UnsafeAnyOrigin/as_unsafe_any_origin视为迁移信号在评审代码时每一处这类用法都值得追问能否用具体 origin 参数化函数签名替代因为它是明确标记为将被移除的构造注意类型名演进提案示例使用UnsafePointer这一拼写当前仓库标准库中该指针类型名为Pointer见 pointer.mojoorigin 作为其类型参数Pointer[T, Origin]的使用方式不变。阅读旧文档或本提案示例时两者指同一 API诊断输出已对齐新名编译器 IR 的 origin 打印LITUtils.cpp直接输出MutUntrackedOrigin、MutUnsafeAnyOrigin等调试与阅读编译器诊断时可直接按新名对照。七、小结这份提案通过一次命名调整把 Mojo origin 系统中两类跳出跟踪的 origin 与生命周期分析的关系显式化UntrackedOrigin空 origin是处理程序外内存的正式工具语义清晰、合法受支持UnsafeAnyOrigin全值 origin则是永不稳定、注定移除的临时逃生舱其每一处使用都应当被视为待迁移点。对开发者而言核心判断准则只有一条只要你的引用指向编译器托管的值就让它携带具体 origin如origin_of(x)把生命周期延长、ASAP 销毁和独占性检查交给编译器只有在真正无主的外部内存上才使用UntrackedOrigin。【免费下载链接】mojoThe Modular Platform (includes MAX Mojo)项目地址: https://gitcode.com/GitHub_Trending/mo/mojo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考