Egg:无 GC、无需生命周期标注的内存安全小语言,可编译为 C99
Egg:没有 GC、不写生命周期标注,但内存安全的小语言,编译成 C99
Egg 作者介绍这门可编译为可移植 C99 的小语言:它没有 GC,也无需生命周期标注,并将释放后使用、重复释放和内存泄漏作为编译错误。性能测试中,Egg 的小程序耗时约为 C 的 1.07–2.31 倍;移植 ONCE Campfire 后,实时聊天内存峰值为 35–39 MiB,Rust 版为 134 MiB。Egg 仍处于早期阶段,语法可能变化。
Egg 是 GitHub 开源小语言,编译为可移植 C99;无 GC、无需生命周期标注,UAF/重复释放/泄漏均为编译错误。单一所有者、能力写在签名、线程经 channel 转移。免费无 AFF,需 Ruby 3.0+ 与 C 编译器;性能约 C 的 1.07–2.31 倍,Campfire 内存 35–39MiB。显性门槛是早期语法会变、生态小。
公开网页暂未检出 Egg 的大规模社区口碑;V2EX 原帖刚发且仅 1 回复,样本不足。作者与 Matz/Spinel 有 PR 互动,非跑路盘,但 Spinel issue #11078–#11080 未解、Ruby 写编译器生态小众。需核验 GitHub star/issue 活跃度、license 与独立安全审计,暂无封号超售类前科。
横向对比 Rust/Zig/C/Python:fib 1.53x 接近 Rust 1.62x、Zig 1.51x,sieve/collatz 接近;matmul 2.31x 落后,因越界检查未优化。Campfire 多数路由吞吐超 Rust,但无缓存时慢,内存约 1/4。纵向无历史价格,开源免费;站内同类 65/68 分参照,Egg 实测更扎实但生态早期。
归入「真香好价可锁单」档但仅限收藏关注与小规模试用,不宜生产锁死。语法会变,需核验 license、issue 活跃度、Spinel 修复进度与 Campfire 覆盖率,并补独立安全审计;缺测试与长期维护证据前别重仓。
最近 Matz 合并了我给 Spinel 的一个 PR,顺便介绍一下我在做的语言 Egg: https://github.com/femto/egg
一句话:编译成可移植 C99,没有 GC,但释放后使用、重复释放、内存泄漏都是编译错误,而且不用写生命周期标注。
几个设计重点
- 单一所有者。 每个缓冲区只有一个所有者,借用在最后一次使用时结束,不需要写
'a。 - 没有未定义行为。 整数溢出、除以零、数组越界都会 panic,不会悄悄回绕或踩坏内存。
- 能力写在签名里。 语言核心不能做 IO,也不能分配内存。文件要先声明
uses io, alloc,再由main把这些值往下传,看函数签名就知道它能碰什么。 - 线程之间不共享数据。 有所有者的值通过 channel 在线程之间转移。
长什么样
uses alloc, io
fn greet(io: Io, a: Allocator, name: str) void {
const msg = a.concat("hello, ", name)
defer a.free(msg)
io.print(msg)
}
fn main(io: Io, a: Allocator) void {
greet(io, a, "egg")
}
忘了写 defer a.free(msg),编译不过:
hello.egg:4:11: error[E135]: 'msg' still owns its buffer when 'greet' ends; free it (a.free(msg)), return it or pass it on
去掉 uses 里的 io,也用不了:
hello.egg:6:8: error[E130]: 'greet' uses io (Io.print), but its module does not declare it; add `uses io` at the top of the file
切片还要用,原来的缓冲区就不能改:
const path = line[4..]
a.push(&line, '!') // 可能会挪动底层字节
io.print(path)
slice.egg:7:12: error[E149]: 'line' cannot be changed here: the slice 'path' of it is used later
把 io.print(path) 挪到 push 前面,就能编过。
另外还有带数据的联合类型 union(enum) + switch 解构、defer、泛型容器等,例子在仓库的 examples/ 里。
编译器是用 Ruby 写的
编译器 eggc 大约 1.2 万行,全部用 Spinel(Matz 写的 Ruby AOT 编译器,把 Ruby 子集编译成 C)能编译的那部分 Ruby 写成。平时跑在 CRuby 上,也能用 Spinel 编成原生程序:早期的 v0 这样编过,启动时间从约 350 ms 降到约 5 ms,也不再需要装 Ruby。现在的版本还卡在几个 Spinel 的 issue 上(#11078、#11079、#11080 )。
性能
小程序:时间是 C 的多少倍(i7-9700K,溢出和越界检查全开)
| 程序 | Egg | Rust(开溢出检查) | Zig ReleaseSafe | CPython | Ruby 3.2 |
|---|---|---|---|---|---|
| fib(40) | 1.53x | 1.62x | 1.51x | 95x | 44x |
| sieve,5000 万 | 1.10x | 1.04x | 1.19x | 27x | 23x |
| collatz,300 万 | 1.07x | 1.37x | 1.34x | 55x | 26x |
| matmul,800×800 | 2.31x | 1.11x | 1.23x | 345x | 164x |
matmul 落后,是因为 eggc 还没有把内层循环的越界检查优化掉。
真实应用:用 Egg 移植 ONCE Campfire
为了在真实应用上检验,我在用 Egg 移植 Basecamp 的群聊应用 ONCE Campfire: https://github.com/femto/once-campfire-egg
它直接用 Rails 版生成的数据库,和 Basecamp 官方的 Rust 移植版 输出逐字节一致的 HTML,用官方的 测试工具 对比:每个响应都做内容校验,每条写入都回查数据库。
GitHub Actions ubuntu-latest,应用占 2 核,每秒请求数,两边都开缓存:
| 路由 | Rust | Egg |
|---|---|---|
| 房间页 | 12,710 | 41,833 |
| 侧栏 | 14,287 | 51,243 |
| 搜索 | 14,138 | 50,540 |
| 静态 CSS | 48,060 | 108,761 |
| 发消息 | 1,818 | 1,771 |
先说清楚:不开缓存时 Egg 每次都重新渲染并 gzip 整页,页面比 Rust 慢很多,完整数据在仓库的 docs/benchmark.md。
实时聊天(Action Cable over WebSocket),1000 个客户端在同一个繁忙房间,每条消息都检查是否送达每个客户端:
| Rust | Egg | |
|---|---|---|
| 满负载下每秒完整送达的消息数 | 178 | 195-222 |
| 送达所有客户端的延迟 p50 | 37 ms | 51-56 ms |
| 没送达全部客户端的消息 | 0 | 0 |
| 内存峰值 | 134 MiB | 35-39 MiB |
基本持平,内存约四分之一。为此 Egg 新加了事件循环 net.poller(),底层是 epoll / kqueue / WSAPoll。
现状
还很年轻,语法还会变。Campfire 目前做了官方测试覆盖的路由和完整的实时聊天,其余部分还在做。
只需要 Ruby 3.0+ 和一个 C 编译器:
git clone https://github.com/femto/egg
cd egg
bin/eggc run examples/complex/dijkstra.egg
欢迎 star。
来源:V2EX · 分享创造 · v2ex.com