Go 和 Rust 都号称高并发,为什么你的团队选错了会痛三年
一个后端团队要重写网关,语言选型会上吵了两小时:一派说 Go 上手快、招人容易,一派说 Rust 性能强、没有 GC。最后拍板用了 Rust,六个月后项目延期,两个核心开发离职。复盘时发现,卡点根本不在性能,而在团队没人写过生命周期标注。
这篇文章不站队。它想帮你搞清楚:Go 和 Rust 的差异到底在哪几个具体机制上,这些机制在什么业务场景下会变成真金白银的成本。
一、内存管理:GC 与所有权,决定了你的延迟曲线长什么样
Go 用并发标记清除 GC,开发者不操心释放。代价是 STW(Stop-The-World)虽然被压到亚毫秒级,但 GC 仍在后台占 CPU,内存占用也偏高。Rust 用所有权 + 借用检查,编译期确定释放时机,运行时零 GC 开销。
看一段 Go 代码,问题在哪:
func process(requests []*Request) []*Response {
results := make([]*Response, 0, len(requests))
for _, r := range requests {
resp := handle(r) // 每次分配新对象
results = append(results, resp)
}
return results
}
这段代码本身没错,但高频调用时,handle 里的临时对象会持续给 GC 制造压力。Go 的解法通常是 sync.Pool 复用对象,或者改造成流式处理避免一次性持有整个 slice。关键点是:Go 的性能优化,很多时候是在跟 GC 博弈——减少分配、复用对象、控制堆大小。P99 延迟要求苛刻(比如 1ms 以内)的场景,GC 抖动会成为你反复调参的噩梦。
Rust 的等价写法:
fn process(requests: Vec<Request>) -> Vec<Response> {
requests.into_iter()
.map(|r| handle(r))
.collect()
}
into_iter 拿走所有权,Request 在循环结束后自动释放,没有 GC 参与。但编译器会盯着你:如果 handle 需要借用 r 的某个字段同时又想移动它,借用检查器直接报错。这不是刁难,是逼你在写代码时就理清数据流向。
踩坑记录:某团队用 Rust 写网关,为了绕开借用检查,大量使用 clone(),结果内存拷贝开销比 Go 还高。所有权不是免费的午餐,用不对反而更慢。
二、并发模型:goroutine 的轻与 async/await 的重
Go 的并发是语言内建的。go func() 启动 goroutine,运行时调度器把它们映射到 OS 线程上,M:N 调度。写起来几乎零心智负担:
func fetchAll(urls []string) []string {
var wg sync.WaitGroup
results := make([]string, len(urls))
for i, url := range urls {
wg.Add(1)
go func(idx int, u string) {
defer wg.Done()
results[idx] = fetch(u)
}(i, url)
}
wg.Wait()
return results
}
注意 i 和 url 要作为参数传入,这是经典陷阱——Go 1.22 之前循环变量是共享的,闭包捕获会全部拿到最后一个值。这个坑坑过无数人。
Rust 的并发靠 async/await + 运行时(如 tokio)。同样是并发抓取:
async fn fetch_all(urls: Vec<String>) -> Vec<String> {
let tasks: Vec<_> = urls.into_iter()
.map(|u| tokio::spawn(async move { fetch(&u).await }))
.collect();
let mut results = Vec::new();
for t in tasks {
results.push(t.await.unwrap());
}
results
}
关键差异:Rust 的 async 是零成本抽象,future 不轮询就不执行,没有额外线程。但这意味着你必须显式选择运行时——tokio、async-std、smol,生态分裂。而且 .await 的传染性很强,一个函数变 async,调用链上游全得改。Go 的 goroutine 没有这个问题,同步代码直接 go 一下就行。
三、选型决策:把语言特性翻译成团队成本
到这里,机制差异清楚了:Go 用 GC 换开发效率,Rust 用编译期约束换运行时确定性。选型时把它们翻译成三个问题:
延迟敏感度。 做金融撮合、实时音视频、高频交易,P99 延迟卡在微秒级,GC 抖动不可接受,Rust 的优势是实打实的。做内部管理后台、CRUD API,Go 完全够用,Rust 的性能是浪费。
团队梯度。 Go 的学习曲线平缓,一个 Java 转过来的工程师一周能上手。Rust 的借用检查、生命周期、trait 系统,通常需要两三个月才能写出不别扭的代码。招 Rust 工程师的成本和难度都更高。前面那个团队延期,本质是拿不匹配的人力去啃陡峭曲线。
生态成熟度。 Go 在云原生(K8s、Docker、etcd)、网络服务上生态极强,标准库自带 HTTP、JSON。Rust 在系统编程、嵌入式、WASM 上更强,但 Web 生态(比如 ORM、模板引擎)还在追赶。选 Go 常常是「站在巨人肩膀上」,选 Rust 常常要「自己造轮子」。
一个务实的判断:如果团队里没有至少一个写过 Rust 生产项目的人,别拿核心业务练手。 可以先从边缘工具、CLI、性能热点模块切入,让语言能力和团队能力同步成长。
核心收获与下一步
Go 和 Rust 的分水岭不在「谁更快」,而在你愿意把复杂度放在编译期还是运行期。Go 把复杂度推给运行时和 GC,换来开发速度和团队友好;Rust 把复杂度前置到编译器和类型系统,换来运行时确定性和零开销。
下一步建议:拿你手头最核心的一个服务,用 Go 和 Rust 各写一个最小可用的性能热点模块(比如序列化、路由匹配),跑一遍真实压测。数字比争论有用得多——但记得把「团队多久能维护这段代码」也算进成本里。
本文关键词:Go、Rust、GC、所有权、并发模型