首页
/ Mojo 语言演进实录:`let` 声明移除提案(remove-let-decls)的设计论证与仓库落地验证

Mojo 语言演进实录:`let` 声明移除提案(remove-let-decls)的设计论证与仓库落地验证

2026-09-10 21:53:54作者:毕习沙Eudora

本文基于 Mojo 语言提案《Simplifying Mojo🔥 - let's get rid of let(作者 Chris Lattner,2023 年 12 月,Status: Accepted)展开。该提案是 Mojo 早期语言设计中最具代表性的简化决策之一:彻底移除"具名不可变变量"let,仅保留 var(与 def 中 Python 风格的隐式声明),并把不可变性收束到借用参数(borrowed arguments)与未来的不可变引用(immutable references)上。读完本文,你将完整理解该提案的动机、论点、分阶段落地计划,以及它在当前仓库发布记录(v0.24.1v0.24.2v0.24.4)中的真实执行结果,可直接用于理解 Mojo 现代语法(var/alias 体系)的来龙去脉。

一、背景:Mojo 0.6 时代的 let/var 双关键字设计

Mojo 在开发早期沿用了 Swift 的先例,允许程序员用 letvar 两个关键字自行决定某个局部名字是否可修改。提案明确指出,Mojo 0.6 及更早版本中的这一设计存在一系列"特别令人意外且不讨喜"(particularly surprising and unfortunate)的方面,归纳起来有八条:

  1. 对 Python 程序员完全是陌生概念。具名不可变变量对 Python 程序员而言是全新概念,而 Swift 的实践经验表明,这往往成为 Swift 初学者需要学习的第一个概念(Constants and Variables)。但具名不可变变量并非核心编程概念,也不是达成 Mojo 目标所必需的。
  2. let 的命名引发大量早期争论letvalconst 等关键字在不同语言(如 C/C++、JavaScript 的 const)中各有取舍,let 的命名本身就是一个持续消耗社区精力的 bikeshed 话题。
  3. 与编译期值 alias 概念重叠。Mojo 还拥有编译期值(compile time value,alias),因此同一套语言里同时存在 aliasletvar 三个相近概念。多数场景下,其他语言(如 JavaScript)中用 const 表达的意图,在 Mojo 中用 alias 表达反而更贴切。
  4. 与 Python 的设计重心相悖。Swift 与 Rust 都鼓励不可变:Swift(以及当时的 Mojo)会对不必要的可变性发出警告,Rust 则让可变性更啰嗦(let mut)。而 Python 压根没有这个概念——若 Mojo 将不可变设为默认,会显得怪异;若不做默认,又何必保留它。
  5. 没有可验证的性能收益。提案作者明确表示没有看到任何数据显示 let/var 区分能带来安全或可读性收益。唯一的论点是看到 let x = foo() 时知道 x 永不被重新赋值,但这种收益很小。
  6. 引用语义类型下极度困惑。不可变性只作用于局部值本身,对引用语义类型(如当时的 Pointer,以及未来 Mojo 的所有类)会造成强烈误导。社区高频提问:"我明明在修改指针指向的目标,为什么警告我该把 var 指针改成 let?"
  7. 不允许 let 作为结构体字段,规则不一致。Swift 为结构体字段初始化制定了非常复杂的规则,Mojo 并不想复刻。同时,默认字段值也没有好的定义方式,例如提案中的设想代码:
struct Thing:
    # 当前并不支持,但可以设想:
    let field = 42
    def __init__(out self):
        self.field = 17  # 是否不应允许覆盖 field?
  1. 与所有权/生命周期体系形成概念叠加。Mojo 拥有所有权(ownership)概念,未来还会有生命周期(lifetimes)与安全引用(包括可变与不可变引用)。不可变性以多种形态漂浮在语言中并不健康,而不可变借用与不可变引用恰恰是真正需要的。

提案作者还以 Swift 主要设计者之一的身份做了主观反思:Swift 为了提升安全而加入了不少相当挑剔的语言特性(例如要求所有可能抛错的值用 try 标记),且许多早期决策缺乏数据支撑。因此,应尽力让 Mojo 保持易学,并尽可能消除多余概念。

二、提案核心:彻底消除 let 声明

提案的核心主张非常直接:彻底消除"具名不可变值"这一概念。这并不会消灭 Mojo 中的不可变性,而是把它推入借用参数与不可变引用的世界中。该决策同时带来两大块收益:

简化概念模型

  1. 消除 Python 程序员必须学习的一个陌生概念;
  2. 直接消除 letalias 之间的混淆;
  3. 消除关键字命名之争(keyword bikeshedding)这一争论源泉;
  4. 消除工作簿(workbook)中"顶层值声明为 let 却实际上可变"的困惑。

削减编译器复杂度

  1. 错误信息不再需要小心翼翼地分辨该说 let 还是 var
  2. IR 表示不再需要为后续语义检查跟踪该信息;
  3. 无需再为支持 let 而实现缺失的功能;
  4. 无需再实现"检测未被修改的 var 并警告改成 let"这一整套逻辑;
  5. 由于 ASAP 析构(ASAP destruction),CheckLifetimes 不得不额外增加复杂度去拒绝形如 let x: Int; x = 1; use(x); x = 2; use(x) 的代码——尽管第一个 x = 1 的生命周期天然结束、x 在再次赋值前本就处于未初始化状态。提案认为这始终是一个设计瑕疵,且实际实现效果并不正确。

提案同时给出明确结论:该提案就目前所知不会对运行时性能产生任何影响——移除的是语言层面的语法与语义负担,而非底层执行能力。

补充:提案中提到的"未来的 lifetimes 特性"在当前仓库中已有对应设计文档 lifetimes-and-provenance.md,与"不可变性主要落在借用与引用层面"的路线一脉相承。

三、var 何去何从?

提案明确表示:如果移除 let,应原样保留 var。理由有二:

  • 与传统 Python 行为不同,var 引入的是显式声明且词法作用域(lexically scoped)的值——Mojo 需要某种引入符(introducer),也确实需要作用域声明;
  • var 这个名字争议更小,因为它比 let 代表"具名常量"更清晰地表示"变量"(variable)。若有人想重命名 var,那是与本提案正交的独立讨论,应分开进行。

四、平滑落地:四阶段推出计划

为了让社区迁移更平滑、破坏性更小,提案设计了一个分阶段推进路线:

  1. 阶段一——社区共识:与 Mojo 社区建立共识,收集反馈与额外视角;
  2. 阶段二——工程验证与警告期:完成工程工作,验证不存在性能回退,并移除 let 的 IR 表示与行为。此阶段仍保留对 let 的解析以维持兼容:将其解析为与 var 相同的 IR,但发出警告"let 已被弃用,将在下个版本移除",并附带把 let 重命名为 var 的 fixit 提示;
  3. 阶段三——约 1 个月后:把警告升级为错误;
  4. 阶段四——约 1 个月后再过一版:连同错误信息一起,彻底移除该关键字。

五、替代方案:保留并日后重估

提案也考虑了替代方案:可以一直保留 let 并在日后重新评估。但作者判断届时不会有任何变化——Modular 内外部的 Mojo 用户社区已经多次踩到这些问题,这一问题会持续出现。该提案最终被标记为 Accepted(已接受),并在后续版本中落地。

六、仓库实证:提案在发布记录中的真实落地过程

原提案是设计蓝图,而当前仓库的 release notes 则记录了它的完整执行轨迹——恰好严格遵循了"警告 → 错误 → 彻底移除"的三步节奏:

v0.24.1(编译器移除 let 声明支持)v0.24.1.md 的 Changed 章节写明:"作为移除 let 声明的一步,我们已在编译器内部移除对 let 声明的支持。为便于迁移,我们把 let 声明解析为 var 声明,因此你的代码不会损坏。我们会对此发出警告,但请显式改用 var,因为该迁移支持将在后续更新中移除。"并给出示例:

fn test():
    # 被当作 var 处理,但请更新你的代码!
    let x = 42  # 警告:'let' 正在被移除,请改用 'var'
    x = 9

v0.24.2(警告升级为编译期错误)v0.24.2.md 记载:"let 声明现在产生编译期错误而非警告,这是我们移除 let 声明的下一步。编译器暂时仍识别 let 关键字,以便产生良好的错误信息,但将在后续版本中移除。"

v0.24.4(关键字从语法中彻底消失)v0.24.4.md 宣告终点:"let 关键字已从语言中完全移除。我们此前已移除 let 声明但仍保留错误信息,现在它彻底从语法(grammar)中消失。"

这一演进路径与提案第五节"Rolling out this proposal smoothly"的设计完全吻合:先在编译器内部消掉语义(解析为 var 的 IR + 警告 + fixit),再升格为错误,最后连关键字与报错一起清除。

七、现状观察:varalias 的现代 Mojo 写法

从当前仓库的代码可以直观验证提案落地后的语言形态——现代 Mojo 代码中不再出现 let 声明,命名值统一由 var(运行时可变值)与 alias(编译期值)承担。

例如标准库中的 rebind.mojo 展示了 var 在真实实现中的用法(var lit = ...var rebound = ...),以及通过参数约定(var src: src_type, out dest: dest_type)表达可变/输出语义的方式。而 alias 则在 Mojo/stdlib/std 各模块(如 anytype.mojosimd.mojodtype.mojo 等)中被大量使用,承担编译期常量与类型别名的职责。

由此可以确认提案落地后的清晰分工:

  • var:声明一个显式、词法作用域、可重新赋值的运行时值;
  • alias:声明编译期值(常量、类型别名、编译期元数据);
  • 不可变性:不再以"具名不可变变量"形式出现,而是交由借用参数(borrowed)与引用语义表达;
  • def 函数:保留 Python 风格的隐式变量声明。

对于从旧版本 Mojo 迁移的开发者,迁移路径同样清晰:把 let x = ... 直接改写为 var x = ...(语义等价,只是不再有不可变保证),把原本想表达编译期常量的 let 改为 alias;若确实需要不可变的借用视图,则依赖借用参数与未来的引用/生命周期特性(参见 lifetimes-and-provenance.md)。

结语:一次教科书式的语言简化决策

remove-let-decls 提案的价值不只在"删掉一个关键字"本身,更在于它示范了 Mojo 语言设计的方法论:以 Python 程序员的心智模型为锚点,以编译器复杂度为成本核算,以社区反馈为验证闭环,以分阶段发布控制迁移阵痛。从 提案文档v0.24.4 发布说明 的完整闭环,既是 Mojo 早期语言演进的一段真实历史,也是理解现代 Mojo 语法体系中 varalias 分工的最佳切入点。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.16 K
2.78 K
kernelkernel
deepin linux kernel
C
34
18
docsdocs
暂无描述
Markdown
904
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
932
1.86 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
862
1.36 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.95 K
1.03 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.38 K
1.47 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
535
606
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
549
398
leetcodeleetcode
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
Markdown
77
23