2026 年 11 月 8 日,一篇名为《You should try Gleam》的开发者博客在技术社区流传。作者本是资深 Rust 玩家,试用函数式语言 Gleam 后发现,自己写的 80% 的代码 只需简单查找替换语法,就能直接贴进 Rust 编译。这种极高重合度让不少苦于 Rust 心智负担的程序员看到了新玩具。但如果顺着作者推荐的三大卖点直接搬上生产环境,等待团队的往往是一场编译器不会报警的运行时崩溃。

先把这门语言的核心家底交待清楚。Gleam 是一门强静态类型的函数式语言,同时支持编译到 BEAM(Erlang 虚拟机)和 JavaScript(通过 Node.js 等运行)。

它砍掉了命令式语言里常见的可变变量、结构体方法和循环语句。在 Gleam 里,98% 的遍历 靠迭代器与管道操作符(|>)串联完成,剩下的 2% 复杂逻辑 全靠递归,并依赖编译器的尾调用优化(TCO)来避免爆栈。为了消除回调函数层层嵌套的缩进,它还引入了 use 语法糖,把回调拉平成类似同步代码的写法。

目前 Gleam 已更新至 v1.19.0 版本。在 2026 年 10 月 5 日的编译器公告中,官方宣布不再将代码先编译为 Erlang 源码中间层,并甩出了一份包含 100 个模块、每个模块 100 个函数的合成项目编译基准测试。更早的 2025 年圣诞节版本里,它还补齐了为外部 API 生成类型规范和 TypeScript 声明的能力。

蜜月期的假象与三大卖点的底层排斥

博客作者把管道操作符、use 语法糖、尾调用优化以及跨编译到 JavaScript 并列为最亮眼的招牌。单看任何一项,设计都很克制;可一旦把它们组合起来写业务,底层机制就会发生剧烈排斥。

语法糖虽甜,跨编译到 JS 却易触发 V8 栈溢出(示意图)
语法糖虽甜,跨编译到 JS 却易触发 V8 栈溢出(示意图)
语法糖越甜,运行时的暗礁就越深。

《The Tailless Deno》实战报告就记录过一次典型的生产级踩坑。开发者用 Gleam 编写了一个编译器并跑在 V8 引擎上,结果直接触发了 V8 栈溢出。

当三大卖点同时启用:JS 目标下的尾调用失效链路 1. 源码编写 无循环全靠递归 穿过 use 语法糖 2. 语法糖展开 result.try 等调用 变成嵌套匿名函数 3. 编译器盲区 JS 后端无法识别 直接自递归转循环 4. 运行时崩溃 V8 缺乏原生 TCO 调用栈溢出且无警告 注:Discussion #2721 已于 2025 年 4 月 20 日关闭,官方确认无法在编译期自动验证 TCO 是否生效。

罪魁祸首正是被大肆赞美的 use 语法糖。在 Gleam 中,use 并不是什么底层的协程魔法,它只是把后半段代码直接包进一个匿名回调函数里。当你配合 result.try 或 bool.guard 使用 use,并在下面写递归调用时,这个递归实际上发生在内层匿名函数里,而不再是外层函数的直接自递归。

放到 BEAM 虚拟机上,这不算大事,因为底层运行时有兜底。但一旦你贪图“前后端类型复用”把代码编译到 JavaScript,问题就炸了。

主流 JS 引擎(如 V8)根本没有原生的尾调用优化。Gleam 的 JS 编译器只能靠静态分析,把最简单的“直接自递归”重写成 JS 的 while 循环。只要递归穿过了 use 展开的闭包,编译器就认不出来了,生成出来的就是实打实的嵌套函数调用。

更麻烦的是,这颗雷在开发阶段是静音的。社区曾要求在编译器或语言服务器里加入自动验证 TCO 是否生效的检查(Discussion #2721),但该讨论已于 2025 年 4 月 20 日 因技术方案不可行而直接关闭。至于在 JS 后端支持相互递归(mutual recursion)尾调用优化的 Issue #3830,早在 2024 年 11 月 14 日 就已开启,至今仍躺在路线图规划中。

社区里同样有人抱怨回调机制在异步场景下太难受,在 Discussion #2051 中呼吁引入 async/await。维护者直接拒绝了这个提议,理由是 use 已经提供了类似过程式的体验,强加核心 async 语义会破坏与 Erlang 的互操作性。哪怕官方导览自己都承认,过度使用 use 会让代码可读性下降。

  • 风险.在 JS 编译目标下混用 use 语法糖与深度递归,会导致尾调用优化静默失效并引发线上栈溢出,开发者只能手动放弃 use 退回冗长的 case 表达式。

抢占式 VM 与系统级语言的工程代差

回到那句“80% 代码像 Rust”。我不太买账的是,很多开发者因为代数数据类型(ADT)和 Result 错误处理长得像,就把 Gleam 当成了某种“带垃圾回收的轻量版 Rust”。

BEAM 负责高并发容错,内部嵌套 Rust 攻克微秒级计算(剖面示意)
BEAM 负责高并发容错,内部嵌套 Rust 攻克微秒级计算(剖面示意)

《淮南子》有云:“橘生淮南则为橘,生于淮北则为枳。”同样的 Result 和管道语法,长在原生机器码上和长在 BEAM 虚拟机上,结出的完全是两种工程果实。

Rust 的并发建立在以 Tokio 为代表的协作式调度之上,追求的是极致的 CPU 吞吐与内存控制上限;而 Gleam 寄生在 BEAM 虚拟机里,继承的是 抢占式调度 与 Actor 容错模型。原文作者畅想用 Gleam 的 OTP 进程树直接平替掉团队里繁琐的 Go 调度器与 Kubernetes 集群,这在架构逻辑上说得通,但忽略了残酷的工业界分工。

看看真实的大厂是怎么混用这两套体系的。2026 年 3 月 4 日,Discord 公布了其 Elixir 追踪基础设施的技术细节;在更早的工业基准里,Discord 恰恰是在 BEAM(Elixir)体系内引入 Rust 编写的有向集合(Ordered Set),才把 5,000 至 1,000,000 个元素 的集合操作耗时压到了 0.61–3.68 微秒。BEAM 负责高并发连接与容错兜底,Rust 负责啃硬核的 CPU 与内存密集型计算,两者从来不是谁替代谁的关系。

从技术玩具到生产落地:三组核心工程数字对照 Discord Rust+Elixir 集合耗时 0.61-3.68μs 处理 5千至100万个元素 BEAM 并发需靠 Rust 补算力 2024 社区调查生产环境占比 52 / 624 受访者中仅 52 人用于生产 Uncover 自 2024.04 构建查询引擎 2026.10 编译器合成基准规模 100 × 100 100 个模块 × 100 个函数 已弃用 Erlang 源码中间层

2026 年的选型账本:谁该为静态类型买单

既然都是跑在 BEAM 上,为什么不去用生态极其成熟的 Elixir,非要选 Gleam?答案只有一个:你对动态类型有洁癖,把编译期静态类型安全视为不可妥协的刚需。

官方 OTP 库尚未覆盖全部特性,生态复用仍有门槛(示意图)
官方 OTP 库尚未覆盖全部特性,生态复用仍有门槛(示意图)

但为这份刚需买单之前,得先算清生态折价。

在生产环境采用度上,Gleam 依然处于早期阶段。在 2024 年的社区调查中,624 名 回答该问题的受访者里只有 52 名 生产环境用户。虽然也有像 Uncover 这样的公司在 2025 年 12 月 3 日发布案例研究,披露其自 2024 年 4 月起就用 Gleam 构建核心查询引擎,但这类吃螃蟹的团队终究是少数。

如果你指望靠“BEAM 生态互操作”直接白嫖 Erlang 和 Elixir 的现成轮子,实际工程体验也会给你泼冷水。目前 Gleam 官方的 OTP 库(gleam-otp)还没有覆盖 Erlang OTP 的全部特性;更要命的是,Elixir 生态大量核心库重度依赖宏(Macro),而 Gleam 无法直接调用 Elixir 宏 API,工程师必须自己动手写一层 Elixir 包装代码才能桥接过来。

把那篇博客当作周末玩具的入坑指南完全没问题,半小时的交互式导览确实能让人心情愉悦。但如果你的团队正打算拿它重写核心服务,不妨先把那 80% 的 Rust 既视感放一放,去查查 Issue #3830 的进度,再看看团队里有没有人愿意在 JS 端为了躲避栈溢出,把漂亮的 use 一行行改回多层嵌套的 case。