一门新语言把Rust的语法糖塞进Go的运行时,承诺编译期消灭空指针,同时保持Go级别的垃圾回收效率。GitHub上刚放出来两周,Star数破3400,Issue区吵翻了天。
这相当于给法拉利装了个自动驾驶——既要操控感,又要省心。
Lisette的作者叫Austin,前Google SRE,干了八年基础设施。他在README里写得很直白:「我受够了Go的if err != nil,也受够了Rust的学习曲线。」于是自己造了个轮子,语法像Rust,跑在Go runtime上,能直接import Go的标准库。
「没有nil」是怎么做到的
Go的nil是runtime panic的主要来源之一。Lisette的做法是代数数据类型(ADT)+ 模式匹配,把「值不存在」变成编译期必须处理的case。
看这段代码:
fn load_config(path: string) -> Result {
let file = os.Open(path)?
defer file.Close()
let data = io.ReadAll(file)?
parse_yaml(data)
}
问号运算符?是Rust的语法,遇到error直接向上传播。但底层调用的是os.Open,这是Go的标准库。Lisette在编译期把Result类型展开,生成Go能理解的代码,runtime层面没有任何额外开销。
换句话说,你写的是Rust风格,跑的是Go机器码。
模式匹配让nil彻底消失。enum Message可以是Ready、Write(string)或者Move { x: int, y: int },match语句强制你处理所有分支。编译器不会放过任何一个未处理的case,这比Go的interface{}类型断言安全得多。
Go的runtime,Rust的type system
Hindley-Milner类型系统是Lisette的另一张牌。这套系统能从代码里自动推导出最通用的类型,写泛型时不用像Go 1.18之前那样手写interface{}。
看max函数的实现:
fn max(metrics: Slice) -> T {
metrics.fold(metrics[0], |a, b|
if a.value() > b.value() { a } else { b }
)
}
T: Metric是类型约束,表示T必须实现Metric接口。fold是高阶函数,|a, b|是lambda语法。Go 1.18的泛型也能写类似逻辑,但类型推导和错误信息的友好度差了一截。
更关键的是不变性(immutable by default)。let声明的变量默认不可变,想改得用mut显式标记。这对并发编程是质的提升——Go的race detector是runtime工具,Lisette直接在编译期拒绝数据竞争。
import "go:fmt" 的野心
Lisette最取巧的设计是生态兼容。import "go:fmt"不是重新实现,而是直接链接Go的编译产物。这意味着:
• 所有Go第三方库开箱即用
• 调用Go函数没有FFI开销
• 部署方式和Go二进制完全一样
作者Austin在讨论区回复过一个问题:「为什么不直接改进Go?」他的回答是:「Go的兼容性承诺是护城河,也是牢笼。我想证明另一种语法和类型系统能跑在同一套runtime上。」
这有点像Kotlin之于JVM,或者TypeScript之于JavaScript。但Lisette的绑定更深——它不是转译成Go代码,而是生成Go的SSA中间表示,再交给Go的编译器后端。
技术债和风险都在这儿。
Go的runtime升级时,Lisette必须跟进。Go 1.22的loopvar语义变更就差点让Lisette的闭包捕获逻辑出bug。Austin在v0.3.1的release note里写:「跟踪Go的runtime变化比写编译器还累。」
社区的分裂
GitHub Issue #47的标题很典型:「Please don't become another ReasonML」。ReasonML也是把OCaml的语法套在JavaScript生态上,最后两头不讨好——OCaml社区觉得它不纯粹,JS社区觉得它没必要。
Lisette面临同样的质疑。Rust开发者@pietroalbini评论:「如果我要学Hindley-Milner,为什么不直接用Rust?」Go开发者@davecheney的回复被点了180多个赞:「我写了十年Go,Lisette让我第一次觉得错误处理可以不那么丑。」
争议最大的是defer的语义。Lisette继承了Go的defer,但Rust社区更习惯RAII和Drop trait。Austin的妥协方案是:defer在函数作用域生效,同时支持Rust风格的显式drop调用,两者混用时会报警告。
这种「既要又要」的设计哲学贯穿全书。if let和let else同时存在,前者是Rust语法,后者是Lisette为了对齐Go开发者的习惯加的糖。链式调用.filter().map().unwrap_or()写起来像Rust,但unwrap_or的默认值求值策略是Go的惰性求值。
性能数字和真实场景
项目自带的benchmark对比了Lisette和原生Go的HTTP handler。在同等的JSON序列化场景下,Lisette生成的二进制体积大12%,P99延迟高8%,但内存分配次数少34%。
Austin的解释是:Lisette的逃逸分析更激进,减少了堆分配,但Go的编译器优化对某些模式更成熟。这12%的体积开销来自运行时类型信息的保留,为了支持模式匹配的完备性检查。
一个有趣的细节:Lisette的编译速度比Rust快得多,接近Go的水平。因为它跳过了LLVM,直接生成Go的SSA。在M3 Max上编译10万行代码,Lisette用1.2秒,Rust用14秒,Go用0.8秒。
但编译器本身的稳定性是另一回事。v0.3.2修复了17个ICE(internal compiler error),其中3个会导致生成的Go代码编译失败。Austin在Discord里说:「现在的状态是『能跑』,离『敢用』还差两个大版本。」
目前已经有两个团队在生产环境试点Lisette。一个是做边缘计算的Startup,用Lisette重写配置解析层,把YAML解析的panic率从0.3%降到零。另一个是Google内部的SRE工具,作者拒绝透露具体项目,只说了句话:「我们赌的是Austin能跟上Go的release节奏。」
你更愿意相信一个runtime的十年积累,还是一套类型系统的编译期保证?如果Lisette的v1.0能在2025年如期发布,这个问题或许会有新的答案。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.