Skip to content
Published on

Dart 宏取消一年半后 — 承诺的三件事真的交付了吗

分享
Authors

引言 — 在 I/O 2026 上消失的词

配合 2026 年 5 月 Google I/O 发布的 Dart 3.12 公告 里,一次都没有出现「宏(macro)」这个词。Dart 3.11 公告(2026 年 2 月)里没有,Dart 3.10 公告(2025 年 11 月)里也没有。而就在整整两年前的 I/O 2024 上,这还是一个被冠以「已经投入多年设计」之名、占据发布公告一大块篇幅隆重介绍的功能——如今却是彻底的沉默。

发生了什么是已知的——宏在 2025 年 1 月被正式取消。本文要做的不是复盘那次事件,而是事后核验。取消公告留下了三个「我们会转而做这些」的承诺,如今一年半过去(以 2026 年 7 月 9 日发布的 Flutter 3.44.6 + Dart 3.12.2 为准),正是核对这些承诺是否兑现的好时机。核验只使用一手资料,不用二手解读——dart.dev 博客原文(站点仓库里的 Markdown)、Dart SDK 与 build_runner 的变更日志、pub.dev 注册表 API、以及 GitHub issue 状态。

时间线 — 发布、警报、取消

2024 年 5 月 14 日,Dart 3.4。 在 I/O 2024 上,宏预览版发布。第一个宏只有一个 @JsonCodable(),用来消除 JSON 序列化的样板代码,计划分三个阶段——JsonCodable 预览 → 如果反响好就把 JSON 宏转为 stable → 最终开放给社区自行定义宏。有意思的是,这篇文章直接提到了数据类。数据类是 Dart 语言仓库里获票最多的功能请求,团队表示「标准该是什么样,大家意见不一」,因此选择不把它内置进语言,而是通过宏系统让社区各自实现。宏从一开始就是数据问题的代理方案。

2024 年 8 月 6 日,Dart 3.5。这篇兼作路线图更新的公告里,警报已经响起。公告说,语言与编译器团队的大部分时间都投入在宏上,但因为可能给 hot reload 这类核心使用场景带来回归,他们正谨慎推进,并写道「距离能分享下一步进展,恐怕还需要好几个季度」。预览版发布还不到三个月,时间表就已经退到了「好几个季度以后」。

2025 年 1 月 29 日,取消。 以 Dart 团队 Vijay Menon 署名的 An update on Dart macros & data serialization 发布。核心的是两句话:「每解决一个重大技术障碍,就会冒出一个新的」,以及「we will not be shipping macros in the foreseeable future(在可预见的未来,我们不会交付宏)」。从发布到取消,间隔了八个半月。

为什么会夭折 — 语义内省与热重载的冲突

取消公告给出的原因既不是性能,也不是人力,而是设计目标本身的冲突。

Dart 团队判断,像 Rust 的过程宏那样只看程序语法(syntax)的宏,不足以真正解决目标问题(比如数据序列化),于是选择了在编译期连类型和声明结构都查询的深度语义内省(semantic introspection)。问题在于这个选择的代价。宏查询程序的语义信息,意味着每次代码变动,都要重新计算依赖那份语义的宏输出。用取消公告的话说,这个代价让「stateful hot reload 很难保持『热』」,当时的实现同时让编辑体验(静态分析、代码补全)和增量编译(hot reload 的第一阶段)两者都发生了回归

站在 Flutter 开发者的角度,这笔交易根本不划算。Dart 团队自己在取消公告里把 Dart 的身份定义为两件事——静态语言级别的 AOT 性能,以及动态语言级别的开发周期(hot reload)。宏再怎么消除样板代码,只要每次保存都让重载变卡,就是在啃食 Flutter 相对于 React Native 或 Compose 所标榜的核心 DX 资产。以机会成本为由退回,从这个角度看是可以理解的决定。

取消公告转而承诺了三件事:(1) 面向数据处理的「更定制化(bespoke)的语言特性」,(2) build_runner 的改进,(3) 作为宏的副产品被原型化的 augmentations 独立交付。接下来逐一打分。

成绩单一 — 面向数据的语言特性:部分交付

取消之后的几次发布里,确实出现了一些小的语言特性。这些都可以在 SDK 变更日志和发布公告里核实到。

  • null-aware elements — 在 Dart 3.8(2025 年 5 月 20 日)交付。让你在集合字面量里写一个「若为 null 就整体剔除」的元素。
  • dot shorthands — 在 Dart 3.10(2025 年 11 月 12 日)交付。当类型能从上下文推断出来时,可以写 .error 而不是 LogLevel.error 的缩写语法。
  • private named parameters — 在 Dart 3.12(2026 年 5 月 20 日)交付。消除了此前用命名参数初始化私有字段时被迫写的初始化列表样板代码。
  • primary constructors — 在 Dart 3.12 中以 experimental 形式发布(需要 --enable-experiment=primary-constructors 标志)。而在尚未发布的 3.13 的 SDK 变更日志 里,它已经被列为正式语言特性。

primary constructors 是这条脉络里的重头戏。它把字段声明和构造函数参数合并进类头的一行里。

// 至今为止的 Dart —— 两个字段的类要写五行
class Point {
  final int x;
  final int y;
  Point(this.x, this.y);
}

// primary constructor —— 3.12 experimental,计划 3.13 转正
class Point(final int x, final int y);

但如果打分打得诚实,这只能算「部分交付」。宏原本要解决的问题——能自动生成 toJson/fromJson==/hashCode/copyWith数据类——依然不存在于这门语言里。dart-lang/language#314(Add data classes)自 2017 年 10 月开出以来,写这篇文章时依然是 open 状态,以 1,552 个赞仍是排名最靠前的请求之一。到目前为止交付的只是减少样板代码的语法,而不是能生成序列化代码的东西。这项工作依然由下文的代码生成来承担。

成绩单二 — build_runner:最确定无疑的交付

由于宏的替代方案归根结底就是「让 build_runner 更快」,这一项是最重要的。而这一项可以用数字来核实。

把 pub.dev 注册表 API 的发布历史原样搬过来看,取消之前的 build_runner 实际上处于维护模式。从 2.4.0(2023 年 1 月 18 日)到 2.4.15(2025 年 2 月 11 日),25 个月内发布 16 个,全是补丁版本,一次次版本升级都没有。取消之后是拐点——从 2.5.0(2025 年 6 月 16 日)到 2.15.2(2026 年 7 月 13 日),13 个月内发布 30 个,其中次版本升级就有 11 次。取消公告许下的投入,就这样原样印在注册表数据里。

内容也不是补丁级别的。仅 2026 年上半年,按 build_runner 变更日志 就有:

  • 2.13.0(2026 年 3 月)——「小型初始构建提速 1.4 倍,大型增量构建提速最高达 4 倍」。完整的基准数据公开在发布 PR(dart-lang/build#4405)里。
  • 2.14.0(2026 年 4 月)——通过改进用于分析的文件管理,「增量构建再提速 2 倍」。构建器编译默认从 JIT 改为 AOT(使用 dart:mirrors 的构建器会回退到 JIT),并明确指出了这个权衡:初次启动变慢,换来后续构建变快。
  • 2.15.0(2026 年 4 月)——声称基线内存占用有所改善,直接移除了 --low-resources-mode 标志。

数字需要贴上标签。以上的 1.4 倍、4 倍、2 倍,全都是Dart 团队自测的数值。测量条件是对应发布 PR 里的基准测试套件,没有任何保证你的代码库——你的构建器组合、包的数量、工作区结构——能跑出同样的倍数。4 倍这个上限,是附带「大型增量构建」这个条件的数值。独立复现的基准测试我目前还没见过。不过方向本身,是变更日志里几十个条目一致指向的结果,没有理由怀疑改进本身的存在。

成绩单三 — augmentations:未交付

取消公告曾说 augmentations(允许在另一个文件里增强某个声明的功能——为把代码生成的产物比 part 文件更平滑地拼接进来打基础)「在宏原型化的功能里具有独立价值,目标是独立交付」,但截至本文写作时,它没有出现在任何一次发布里。追踪 issue dart-lang/language#4154 依然是 open 状态(最后一次更新在 2026 年 2 月),直到尚未发布的 3.13 区间为止的 SDK 变更日志里,也没有 augmentation 相关条目。这是三个承诺里唯一一个什么都没到的。

顺带一提,build_runner 改进的上级追踪 issue(dart-lang/build#3800,"Faster dev cycle")也依然是 open 状态——这意味着交付仍在进行中,而不是已经完成。

一个已故功能留下的痕迹

取消得有多彻底,从副产物的清理方式上就能看出来。以下这些现在都可以核实。

  • pub.dev 上的 macros(停留在 0.0.1),以及承载 JsonCodable 的 json(0.20.4,最后更新于 2024 年 12 月),都被标上了 discontinued
  • 曾经的文档页面 dart.dev/language/macros,如今会 301 重定向 到取消公告的博客文章。功能文档的 URL 直接连到该功能自己的讣告,这种景象并不常见。

与此同时,宏原本想要取代的那些东西活得好好的。按 pub.dev 上的数据,json_serializable 仍在发布,版本到了 6.14.0(2026 年 5 月 15 日),freezed 则是 3.2.5(2026 年 2 月 3 日)。2026 年的 Dart 数据序列化依然是代码生成,变的只是驱动这套代码生成的引擎变快了。

那么现在该做什么

如果你有决定一直在等宏、迟迟没定,把它了结掉。 官方立场是「在可预见的未来都不会有」,最近三次发布公告(3.10、3.11、3.12)里,宏甚至连提都没被提到。虽然有一个长期元编程探索的 issue(#1482)还开着,但路线图上什么都没有。

把 build_runner 升到最新版本是笔便宜的收益。 如果你的项目还停留在 2.4.x,一路升到 2.15.x 之间已经叠加了好几层性能改进。不过要注意,从 2.14.0 起 AOT 编译成为默认设置,首次启动可能会变慢,而如果有依赖 dart:mirrors 的构建器,会触发 JIT 回退——升级前最好先了解这一点。

primary constructors 先别急着开启。 在 3.12 中它仍处于 experimental 标志之下,而 experimental 特性的语法可能在发布之间发生变化。虽然 3.13 的变更日志里已经正式列出了它,但 3.13 还没有发布——未发布的变更日志是计划,不是承诺。

没有理由拆掉你的 freezed/json_serializable 技术栈。 在语言内置数据类那一天到来之前(#314 从 2017 年起就一直开着),代码生成就是 Dart 数据故事的全部。这套技术栈在整个 Flutter 生态里所处的位置,正如跨平台对比文章中所述。

结语

功能被取消通常都是悄无声息的——issue 被搁置几年后关闭,文档悄悄消失。Dart 宏的取消值得回顾,恰恰是因为它反其道而行之——用技术语言解释了原因(语义内省的编译期成本与 hot reload 不兼容),明确列出了三个替代方案,其中两个在一年半后,正以可以用发布记录和注册表数据核实的形式交付着。

与此同时,这份成绩单还不完整。augmentations 尚未到来,数据类依然是 issue 追踪器上排名第一的请求,build_runner 的速度数字目前也仍然只是团队自测。如果宏的死亡留下了什么教训,大概是这一点——hot reload 是 Dart 不会拿任何语言特性去交换的不可侵犯的资产,而在这个约束之下,Dart 的元编程当下会一直停留在「更快的代码生成」这个答案上。

参考资料