Bun 六天重写:96万行代码从 Zig 迁移到 Rust,系统编程语言王座易主的信号
背景:一个 JavaScript 运行时的语言赌局
2022年夏天,Bun 横空出世,用一个令人瞠目的选择震动了前端圈:它用 Zig 语言编写。在 Rust 已经成为系统编程新宠的时代,Jarred Sumner 选择了一条少有人走的路。
Zig 有它的吸引力——没有隐式控制流、没有隐藏内存分配、comptime 编译期计算,听起来就是为高性能运行时而生的。Bun 也确实用 Zig 创造了奇迹:npm install 速度比 npm 快 30 倍,启动时间比 Node.js 快 4 倍,一时间风头无两。
但四年后的 2026 年 5 月 11 日,Jarred Sumner 在社交媒体上宣布:Bun 即将被 Rust 重写版本取代。96 万行 Zig 代码,6 天迁移完成,Linux x64 glibc 环境下通过 99.8% 的现有测试套件。
这不是一个简单的语言迁移故事。这是一面镜子,照出了系统编程语言生态的真实格局,也照出了 AI 辅助编程对软件工程的深层影响。
核心概念:为什么要从 Zig 迁移到 Rust?
Zig 的困境:语言设计与工程现实的撕裂
Zig 作为一门语言,设计哲学非常优雅。它的 comptime 能力让编译期计算变得自然,没有宏的复杂性却能达到类似的效果;它的手动内存管理比 C 更安全,却不引入 Rust 那样的借用检查器心智负担。
但在工程实践中,问题逐渐暴露:
1. 内存泄漏——Zig 的阿喀琉斯之踵
Zig 没有垃圾回收,也没有 Rust 那样的所有权系统。它依赖程序员手动管理内存,用 allocator 接口让分配和释放成对。理论上这很完美,实际上呢?
// Zig 的内存分配模式
const Allocator = std.mem.Allocator;
fn processRequest(allocator: Allocator, req: *Request) !Response {
const buffer = try allocator.alloc(u8, 4096);
defer allocator.free(buffer); // 必须记得 free
const result = try doSomething(allocator, req);
// 如果 doSomething 内部也分配了内存,
// 谁负责释放?调用者还是被调用者?
// Zig 没有强制约定,全靠文档和约定
return result;
}
问题在于:当项目规模达到 96 万行代码,defer allocator.free() 不再是万能解。错误路径上的内存泄漏、跨模块的内存所有权传递、异步代码中的生命周期管理——这些在 Rust 中由借用检查器强制保证的事情,在 Zig 中全靠人工审查。
Bun 在与 Claude Code 深度集成后,内存泄漏问题变得尤为突出。一个 JavaScript 运行时需要长时间运行,内存泄漏会随时间累积,最终导致性能退化甚至崩溃。
2. 生态系统差距
Rust 的 cargo 生态系统在 2026 年已经极其成熟。crates.io 上有超过 20 万个 crate,从 HTTP 服务器到数据库驱动,从序列化框架到异步运行时,应有尽有。
Zig 的包管理直到最近才有了官方的 zig package manager,但生态规模远不及 Rust。Bun 团队在开发中经常需要自己造轮子,或者用 Zig 的 C 互操作能力绑定现有的 C 库——这又带来了 FFI 开销和安全风险。
3. 人才招聘的现实
这是一个很少被公开讨论但极其关键的因素。Rust 开发者的招聘池远大于 Zig。Bun 团队要扩张,找到优秀的 Zig 开发者非常困难;而 Rust 在后端和系统编程领域已经被广泛采用,人才储备充足。
Rust 的优势:为什么它成了替代者?
1. 所有权系统——编译期内存安全保证
// Rust 的所有权模型
fn process_request(req: &Request) -> Result<Response, Error> {
let buffer = vec![0u8; 4096]; // 所有权明确
let result = do_something(&buffer, req)?; // 借用,不转移所有权
// buffer 在作用域结束时自动释放
// 编译器保证不会 double free、use-after-free
Ok(result)
}
// 跨线程安全
fn spawn_worker(data: Arc<Mutex<SharedState>>) {
std::thread::spawn(move || {
let mut state = data.lock().unwrap();
state.process();
// MutexGuard 自动释放,不会死锁(除非逻辑错误)
});
}
Rust 的借用检查器在编译期就能捕获绝大多数内存安全问题。对于一个需要长时间运行的 JavaScript 运行时,这意味着内存泄漏的概率大大降低。
2. async/await 和 tokio——异步编程的成熟方案
Bun 作为一个 JavaScript 运行时,异步 I/O 是核心能力。Rust 的 tokio 异步运行时经过多年打磨,在高并发场景下的表现已经过生产验证。
use tokio;
use hyper::{Server, Request, Response, Body};
async fn handle(req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
// tokio 的异步 I/O,零拷贝,高效事件循环
let data = fetch_from_cache(&req.uri().path()).await;
match data {
Some(cached) => Ok(Response::new(Body::from(cached))),
None => {
let fetched = fetch_from_origin(&req.uri().path()).await?;
cache_result(&req.uri().path(), &fetched).await;
Ok(Response::new(Body::from(fetched)))
}
}
}
#[tokio::main]
async fn main() {
let addr = ([127, 0, 0, 1], 3000).into();
let server = Server::bind(&addr).serve(|| {
hyper::service::service_fn(|req| async move { handle(req).await })
});
server.await.unwrap();
}
3. 生态和工具链
Rust 的工具链已经非常成熟:
cargo:包管理 + 构建系统 + 测试框架一体clippy:强大的 lint 工具rust-analyzer:IDE 支持miri:未定义行为检测cargo fuzz:模糊测试
Zig 虽然有 zig build,但在工具链完整度上仍有差距。
架构分析:96万行代码如何6天迁移?
这才是整个事件最令人震惊的部分。96 万行代码,6 天完成迁移,通过 99.8% 的测试。这听起来不可思议,但当我们拆解过程,就会发现这背后的方法论值得每个程序员学习。
迁移策略:不是翻译,是重写+验证
首先澄清一个关键点:这不是"逐行翻译 Zig 到 Rust"。那是不可能完成的任务——两种语言的内存模型、错误处理、类型系统完全不同。
Bun 团队采用的是"行为等价重写"策略:
- 以测试套件为契约:Bun 已有的测试套件是迁移的锚点。只要新代码能通过相同的测试,就认为行为等价。
- AI 辅助生成骨架:利用 Claude Code 生成 Rust 代码骨架,然后人工审查和调整。
- 增量验证:按模块逐步迁移,每个模块迁移完立即跑测试,确保不偏离。
- Ffi 桥接过渡:对于尚未迁移的模块,通过 Rust-Zig FFI 桥接,保证整体系统可运行。
AI 辅助编程的角色
Jarred Sumner 在 HackerNews 上透露,这次重写大量使用了 AI 生成代码。这不是噱头——96 万行代码 6 天迁移,纯人工不可能做到。
AI 在这个过程中的具体角色:
人类工程师的职责:
├── 定义模块接口和行为契约
├── 审查 AI 生成的代码
├── 处理 AI 无法解决的边界情况
└── 架构决策和性能调优
AI(Claude Code)的职责:
├── 根据接口定义生成 Rust 实现骨架
├── 批量转换 Zig 模式到 Rust 模式
│ ├── alloc/dealloc → ownership/borrowing
│ ├── error unions → Result<T, E>
│ ├── comptime → const generics / macros
│ └── Zig test → #[test]
├── 生成单元测试
└── 自动修复编译错误
这种分工模式是 2026 年软件工程的新范式:人类做架构和决策,AI 做实现和搬砖。效率提升不是线性的,而是数量级的。
关键技术映射:Zig → Rust
让我们看几个具体的技术映射案例:
错误处理
// Zig: Error Unions
const FileOpenError = error{
FileNotFound,
PermissionDenied,
OutOfMemory,
};
fn openFile(path: []const u8) FileOpenError!File {
const file = try std.fs.cwd().openFile(path, .{});
return file;
}
// Rust: Result<T, E> + thiserror
#[derive(Debug, thiserror::Error)]
enum FileOpenError {
#[error("file not found: {0}")]
NotFound(String),
#[error("permission denied: {0}")]
PermissionDenied(String),
#[error("out of memory")]
OutOfMemory,
}
fn open_file(path: &str) -> Result<File, FileOpenError> {
std::fs::File::open(path)
.map_err(|e| match e.kind() {
std::io::ErrorKind::NotFound => FileOpenError::NotFound(path.to_string()),
std::io::ErrorKind::PermissionDenied => FileOpenError::PermissionDenied(path.to_string()),
_ => FileOpenError::OutOfMemory,
})
}
编译期计算
// Zig: comptime
fn Matrix(comptime T: type, comptime width: usize, comptime height: usize) type {
return [height][width]T;
}
fn dot(comptime T: type, comptime n: usize, a: *const [n]T, b: *const [n]T) T {
var result: T = 0;
for (a.*, b.*) |ai, bi| {
result += ai * bi;
}
return result;
}
// Rust: const generics + traits
struct Matrix<T, const WIDTH: usize, const HEIGHT: usize>
where
T: Default + Copy,
{
data: [[T; WIDTH]; HEIGHT],
}
fn dot<T, const N: usize>(a: &[T; N], b: &[T; N]) -> T
where
T: Default + Copy + std::ops::Mul<Output = T> + std::ops::Add<Output = T>,
{
let mut result = T::default();
for i in 0..N {
result = result + a[i] * b[i];
}
result
}
内存分配
// Zig: 显式 allocator
fn createServer(allocator: Allocator, port: u16) !*Server {
const server = try allocator.create(Server);
errdefer allocator.destroy(server);
server.* = .{
.port = port,
.connections = std.ArrayList(*Connection).init(allocator),
};
return server;
}
// Rust: 所有权 + Drop
struct Server {
port: u16,
connections: Vec<Connection>,
}
impl Server {
fn new(port: u16) -> Self {
Self {
port,
connections: Vec::new(),
}
}
// Drop 自动实现,无需手动 free
}
impl Drop for Server {
fn drop(&mut self) {
// 自定义清理逻辑
for conn in &mut self.connections {
conn.close();
}
}
}
代码实战:用 Rust 构建一个迷你 JavaScript 运行时核心
为了让大家更深入地理解 Bun 迁移到 Rust 后的架构,我们来构建一个极简的 JavaScript 运行时核心。这个核心包含:事件循环、模块系统、HTTP 服务器。
1. 事件循环
use std::collections::VecDeque;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll, Waker};
/// 任务类型:一个被 Pin 住的 Future
type Task = Pin<Box<dyn Future<Output = ()> + Send>>;
/// 极简事件循环
pub struct EventLoop {
tasks: VecDeque<Task>,
ready_queue: VecDeque<Task>,
}
impl EventLoop {
pub fn new() -> Self {
Self {
tasks: VecDeque::new(),
ready_queue: VecDeque::new(),
}
}
/// 提交一个异步任务
pub fn spawn<F>(&mut self, future: F)
where
F: Future<Output = ()> + Send + 'static,
{
self.tasks.push_back(Box::pin(future));
}
/// 运行事件循环,直到所有任务完成
pub fn run(&mut self) {
// 创建一个简单的 waker,用于唤醒任务
let waker = noop_waker();
let mut cx = Context::from_waker(&waker);
loop {
// 处理就绪队列
while let Some(mut task) = self.ready_queue.pop_front() {
match task.as_mut().poll(&mut cx) {
Poll::Ready(()) => continue, // 任务完成
Poll::Pending => self.tasks.push_back(task), // 重新入队
}
}
// 检查主队列
let mut pending = VecDeque::new();
while let Some(mut task) = self.tasks.pop_front() {
match task.as_mut().poll(&mut cx) {
Poll::Ready(()) => {}
Poll::Pending => pending.push_back(task),
}
}
self.tasks = pending;
if self.tasks.is_empty() && self.ready_queue.is_empty() {
break;
}
}
}
}
/// 创建一个 no-op waker
fn noop_waker() -> Waker {
use std::sync::Arc;
struct NoopWaker;
impl std::task::Wake for NoopWaker {
fn wake(self: Arc<Self>) {}
}
Waker::from(Arc::new(NoopWaker))
}
2. 模块系统
use std::collections::HashMap;
use std::path::{Path, PathBuf};
/// 模块标识符
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct ModuleId(String);
impl ModuleId {
pub fn from_specifier(spec: &str) -> Self {
Self(spec.to_string())
}
}
/// 模块状态
enum ModuleStatus {
Fetching,
Loaded(String), // 源代码
Compiled(Bytecode), // 编译后的字节码
Error(String),
}
#[derive(Clone)]
struct Bytecode {
instructions: Vec<u8>,
// 简化表示,实际 V8/JavaScriptCore 字节码更复杂
}
/// 模块图:管理模块依赖关系
pub struct ModuleGraph {
modules: HashMap<ModuleId, ModuleStatus>,
dependencies: HashMap<ModuleId, Vec<ModuleId>>,
cache_dir: PathBuf,
}
impl ModuleGraph {
pub fn new(cache_dir: impl AsRef<Path>) -> Self {
Self {
modules: HashMap::new(),
dependencies: HashMap::new(),
cache_dir: cache_dir.as_ref().to_path_buf(),
}
}
/// 解析模块说明符为绝对路径
pub fn resolve(&self, specifier: &str, referrer: Option<&ModuleId>) -> Result<ModuleId, String> {
// 处理 node_modules 查找
if !specifier.starts_with('.') && !specifier.starts_with('/') {
return Ok(ModuleId::from_specifier(specifier));
}
// 相对路径解析
let base = referrer
.and_then(|r| Path::new(r.0.as_str()).parent())
.unwrap_or(Path::new("."));
let resolved = base.join(specifier);
let canonical = resolved
.canonicalize()
.map_err(|e| format!("Cannot resolve '{}': {}", specifier, e))?;
Ok(ModuleId::from_specifier(canonical.to_string_lossy().as_ref()))
}
/// 加载模块(带缓存)
pub async fn load(&mut self, id: &ModuleId) -> Result<(), String> {
if self.modules.contains_key(id) {
return Ok(());
}
let path = Path::new(id.0.as_str());
// 检查磁盘缓存
let cache_path = self.cache_path(id);
if cache_path.exists() {
let bytecode = std::fs::read(&cache_path)
.map_err(|e| format!("Cache read error: {}", e))?;
self.modules.insert(id.clone(), ModuleStatus::Compiled(Bytecode {
instructions: bytecode,
}));
return Ok(());
}
// 从文件系统加载
let source = tokio::fs::read_to_string(path)
.await
.map_err(|e| format!("Cannot load module '{}': {}", id.0, e))?;
self.modules.insert(id.clone(), ModuleStatus::Loaded(source));
Ok(())
}
/// 编译模块
pub fn compile(&mut self, id: &ModuleId) -> Result<(), String> {
let source = match self.modules.get(id) {
Some(ModuleStatus::Loaded(src)) => src.clone(),
Some(ModuleStatus::Compiled(_)) => return Ok(()),
_ => return Err(format!("Module '{}' not loaded", id.0)),
};
// 实际中这里会调用 JavaScriptCore 或 V8 的编译 API
let bytecode = self.compile_source(&source)?;
self.modules.insert(id.clone(), ModuleStatus::Compiled(bytecode));
Ok(())
}
fn compile_source(&self, source: &str) -> Result<Bytecode, String> {
// 简化:实际会调用 JSC 或 V8
let instructions = source.as_bytes().to_vec();
Ok(Bytecode { instructions })
}
fn cache_path(&self, id: &ModuleId) -> PathBuf {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
id.0.hash(&mut hasher);
let hash = hasher.finish();
self.cache_dir.join(format!("{:016x}.cache", hash))
}
}
3. HTTP 服务器(Bun 风格)
use hyper::server::conn::AddrStream;
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Method, Request, Response, Server, StatusCode};
use std::convert::Infallible;
use std::sync::Arc;
use tokio::sync::RwLock;
/// HTTP 请求处理器
type Handler = Arc<dyn Fn(Request<Body>) -> Response<Body> + Send + Sync>;
/// Bun 风格的 HTTP 服务器
pub struct BunHttpServer {
port: u16,
handler: Handler,
stats: Arc<RwLock<ServerStats>>,
}
#[derive(Default)]
struct ServerStats {
total_requests: u64,
active_connections: u64,
total_bytes_sent: u64,
errors: u64,
}
impl BunHttpServer {
pub fn new<F>(port: u16, handler: F) -> Self
where
F: Fn(Request<Body>) -> Response<Body> + Send + Sync + 'static,
{
Self {
port,
handler: Arc::new(handler),
stats: Arc::new(RwLock::new(ServerStats::default())),
}
}
pub async fn start(&self) -> Result<(), Box<dyn std::error::Error>> {
let handler = self.handler.clone();
let stats = self.stats.clone();
let make_svc = make_service_fn(move |_: &AddrStream| {
let handler = handler.clone();
let stats = stats.clone();
async move {
Ok::<_, Infallible>(service_fn(move |req: Request<Body>| {
let handler = handler.clone();
let stats = stats.clone();
async move {
let start = std::time::Instant::now();
// 更新活跃连接数
{
let mut s = stats.write().await;
s.active_connections += 1;
s.total_requests += 1;
}
// 调用用户定义的处理器
let response = handler(req);
// 更新统计
{
let mut s = stats.write().await;
s.active_connections = s.active_connections.saturating_sub(1);
s.total_bytes_sent += response.body().size_hint().lower();
}
Ok::<_, Infallible>(response)
}
}))
}
});
let addr = ([0, 0, 0, 0], self.port).into();
let server = Server::bind(&addr).serve(make_svc);
println!("🚀 Bun (Rust) HTTP server listening on port {}", self.port);
server.await?;
Ok(())
}
/// 获取服务器统计信息
pub async fn stats(&self) -> ServerStats {
self.stats.read().await.clone()
}
}
// 使用示例
#[tokio::main]
async fn main() {
let server = BunHttpServer::new(3000, |req: Request<Body>| {
match (req.method(), req.uri().path()) {
(&Method::GET, "/") => {
Response::builder()
.status(StatusCode::OK)
.header("content-type", "text/html")
.body(Body::from("<h1>Hello from Bun (Rust)!</h1>"))
.unwrap()
}
(&Method::GET, "/api/json") => {
let json = r#"{"runtime":"bun","language":"rust","version":"2.0"}"#;
Response::builder()
.status(StatusCode::OK)
.header("content-type", "application/json")
.body(Body::from(json))
.unwrap()
}
_ => {
Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Body::from("404 Not Found"))
.unwrap()
}
}
});
server.start().await.unwrap();
}
性能优化:Rust 版 Bun 的性能策略
1. 零拷贝 I/O
JavaScript 运行时最关键的性能指标之一是 I/O 吞吐量。Rust 的零拷贝能力在这里发挥了巨大作用:
use std::os::unix::io::AsRawFd;
use tokio::io::unix::AsyncFd;
/// 零拷贝文件传输:sendfile 系统调用
pub async fn send_file(
fd: impl AsRawFd,
sock: impl AsRawFd,
offset: usize,
count: usize,
) -> std::io::Result<usize> {
let fd_raw = fd.as_raw_fd();
let sock_raw = sock.as_raw_fd();
// 使用 Linux sendfile 系统调用,内核空间直接传输,无用户态拷贝
let sent = tokio::task::spawn_blocking(move || {
unsafe {
libc::sendfile(sock_raw, fd_raw, std::ptr::null_mut(), count)
}
})
.await??;
if sent < 0 {
return Err(std::io::Error::last_os_error());
}
Ok(sent as usize)
}
/// 零拷贝 HTTP 响应:引用计数缓冲区
pub struct ZeroCopyBuffer {
data: Arc<[u8]>,
offset: usize,
len: usize,
}
impl ZeroCopyBuffer {
pub fn new(data: Vec<u8>) -> Self {
let len = data.len();
Self {
data: data.into_boxed_slice().into(),
offset: 0,
len,
}
}
/// 切片,零拷贝
pub fn slice(&self, start: usize, end: usize) -> Self {
assert!(start <= end && end <= self.len);
Self {
data: Arc::clone(&self.data),
offset: self.offset + start,
len: end - start,
}
}
pub fn as_bytes(&self) -> &[u8] {
&self.data[self.offset..self.offset + self.len]
}
}
2. 内存池:减少分配开销
use std::sync::Mutex;
/// 固定大小的内存池,避免频繁分配
pub struct BufferPool {
pool: Mutex<Vec<Vec<u8>>>,
buffer_size: usize,
max_buffers: usize,
}
impl BufferPool {
pub fn new(buffer_size: usize, max_buffers: usize) -> Self {
Self {
pool: Mutex::new(Vec::with_capacity(max_buffers)),
buffer_size,
max_buffers,
}
}
/// 获取一个缓冲区
pub fn acquire(&self) -> PooledBuffer {
let mut pool = self.pool.lock().unwrap();
if let Some(mut buf) = pool.pop() {
buf.clear();
PooledBuffer {
buffer: Some(buf),
pool: self,
}
} else {
PooledBuffer {
buffer: Some(Vec::with_capacity(self.buffer_size)),
pool: self,
}
}
}
fn release(&self, buf: Vec<u8>) {
let mut pool = self.pool.lock().unwrap();
if pool.len() < self.max_buffers {
pool.push(buf);
}
// 超过上限则丢弃,让操作系统回收
}
}
/// RAII 管理的池化缓冲区
pub struct PooledBuffer<'a> {
buffer: Option<Vec<u8>>,
pool: &'a BufferPool,
}
impl<'a> std::ops::Deref for PooledBuffer<'a> {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target {
self.buffer.as_ref().unwrap()
}
}
impl<'a> std::ops::DerefMut for PooledBuffer<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.buffer.as_mut().unwrap()
}
}
impl<'a> Drop for PooledBuffer<'a> {
fn drop(&mut self) {
if let Some(buf) = self.buffer.take() {
self.pool.release(buf);
}
}
}
3. JavaScriptCore 绑定优化
Bun 使用 JavaScriptCore (JSC) 作为 JavaScript 引擎。迁移到 Rust 后,JSC 的绑定方式也做了优化:
use std::ffi::c_void;
use std::ptr;
// JSC FFI 绑定(简化)
extern "C" {
fn JSEvaluateScript(
ctx: *mut c_void,
script: *const c_void,
this_object: *mut c_void,
source_url: *const c_void,
starting_line_number: i32,
exception: *mut *mut c_void,
) -> *mut c_void;
fn JSValueMakeString(
ctx: *mut c_void,
string: *const c_void,
) -> *mut c_void;
fn JSStringCreateWithUTF8CString(
string: *const u8,
) -> *mut c_void;
fn JSStringRelease(string: *mut c_void);
}
/// 安全的 JSC 评估包装
pub struct JscContext {
ctx: *mut c_void,
}
// JscContext 是线程安全的,因为 JSC 内部有锁
unsafe impl Send for JscContext {}
unsafe impl Sync for JscContext {}
impl JscContext {
pub fn evaluate(&self, script: &str, source_url: Option<&str>) -> Result<JsValue, JsError> {
let c_script = std::ffi::CString::new(script).map_err(|_| JsError::NullByteInScript)?;
let js_script = unsafe { JSStringCreateWithUTF8CString(c_script.as_ptr()) };
let c_url = source_url
.map(|u| std::ffi::CString::new(u).map_err(|_| JsError::NullByteInScript))
.transpose()?;
let js_url = c_url.as_ref().map(|u| unsafe {
JSStringCreateWithUTF8CString(u.as_ptr())
});
let mut exception: *mut c_void = ptr::null_mut();
let result = unsafe {
JSEvaluateScript(
self.ctx,
js_script,
ptr::null_mut(),
js_url.unwrap_or(ptr::null_mut()),
1,
&mut exception,
)
};
// 清理 JSStringRef
unsafe {
JSStringRelease(js_script);
if let Some(url) = js_url {
JSStringRelease(url);
}
}
if exception.is_null() {
Ok(JsValue { raw: result })
} else {
Err(JsError::EvaluationFailed)
}
}
}
#[derive(Debug)]
pub struct JsValue {
raw: *mut c_void,
}
#[derive(Debug)]
pub enum JsError {
NullByteInScript,
EvaluationFailed,
}
Zig 反 AI 贡献政策:另一个维度的信号
在 Bun 从 Zig 迁移到 Rust 的同一时期,Zig 语言官方宣布了反 AI 贡献政策:不接受 AI 生成的代码贡献。这引发了开源社区的激烈争论,Simon Willison 的分析文章在 HackerNews 上获得了 656 个点。
这两件事放在一起看,耐人寻味。
AI 代码贡献的争议
支持 Zig 政策的人认为:
- AI 生成的代码质量参差不齐,审查成本高
- 版权问题模糊——AI 训练数据的版权归属不清
- 社区文化应该鼓励人类深度参与,而非流水线式生成代码
反对的人认为:
- AI 是工具,就像 IDE 的自动补全——禁止 AI 贡献和禁止拼写检查一样荒谬
- 代码质量应该通过代码审查判断,而非通过工具来源
- 这会让贡献者被迫隐藏 AI 使用,导致更不透明
从 Bun 的角度看
有趣的是,Bun 的 Rust 重写恰恰大量使用了 AI 生成的代码。如果 Zig 社区禁止 AI 贡献,那么愿意用 AI 加速开发的贡献者会自然转向其他社区。这在一定程度上加速了人才的流动。
当然,这只是一个侧面因素,不是决定性因素。但它揭示了一个趋势:在 AI 辅助编程时代,语言和框架的竞争力不仅取决于技术特性,还取决于社区对 AI 工具的拥抱程度。
对开发者的影响:我们该学什么?
1. Rust 值得投资
Bun 的迁移是一个强烈的信号:Rust 在系统编程领域的地位越来越稳固。如果你在 Rust 和 Zig 之间犹豫,2026 年的选择很明确。
Rust 的学习曲线确实陡峭,但回报也很丰厚:
- 编译期内存安全,运行时零开销
- 极其丰富的生态系统
- 越来越多的公司采用(AWS、Google、Microsoft、Cloudflare...)
- 薪资水平在系统编程领域名列前茅
2. AI 辅助编程是必备技能
Bun 的 6 天迁移证明了:AI 辅助编程不再是噱头,而是生产力倍增器。掌握如何有效地与 AI 协作,是 2026 年开发者的核心技能。
关键技巧:
- 先定义接口,再让 AI 实现:像 Bun 团队那样,用测试套件和接口定义作为 AI 的约束
- 增量验证:不要让 AI 一次性生成大量代码,逐步验证
- 审查 > 生成:AI 生成的代码必须人工审查,尤其是内存安全和并发逻辑
- 理解原理:不要因为 AI 能生成代码就忽视底层原理——你需要知道什么时候 AI 生成的是错的
3. 语言选择要考虑生态,不只是语法
Zig 的语法设计很优雅,但生态系统才是决定项目成败的关键。选择语言时,要考虑:
- 库和框架的丰富度:能否找到现成的解决方案?
- 人才池的大小:团队能否招到人?
- 工具链的成熟度:调试、测试、性能分析工具是否完善?
- 社区的活跃度:遇到问题能否快速找到答案?
4. 测试套件是最好的迁移保险
Bun 能在 6 天内完成迁移,根本原因是有一套完善的测试套件。这再次证明了测试驱动开发的价值——不是为了 TDD 的教条,而是为了给自己留一条后路。
性能对比:Rust 版 Bun vs Zig 版 Bun
虽然 Rust 版 Bun 还在开发中,但我们可以从架构层面分析性能变化:
| 维度 | Zig 版 Bun | Rust 版 Bun | 说明 |
|---|---|---|---|
| 启动时间 | ~5ms | 预计 ~5ms | 两者都是原生二进制,差异不大 |
| HTTP 吞吐量 | ~300K req/s | 预计 ~350K+ req/s | tokio 的成熟调度器可能更优 |
| 内存占用 | 随时间增长(泄漏) | 稳定 | Rust 所有权模型杜绝泄漏 |
| npm install | ~2s (冷启动) | 预计 ~2s | I/O 密集型,语言差异不大 |
| FFI 开销 | 无(Zig 天然兼容 C) | 有(Rust FFI 有少量开销) | Zig 的 C 互操作更直接 |
| 编译速度 | 快(Zig 编译快) | 慢(Rust 编译慢) | 开发体验的trade-off |
值得注意的是,Rust 的编译速度确实是个痛点。Zig 的编译速度是它的显著优势之一,而 Rust 的编译时间在大型项目中可能让人抓狂。不过,对于运行时性能而言,编译速度并不影响。
未来展望
Bun 的 Rust 未来
Rust 版 Bun 带来了几个新的可能性:
执行 Java 字节码:Jarred 在公告中提到,新的 Rust 版本具备了执行 Java 的能力。这意味着 Bun 可能从一个 JavaScript 运行时演变为一个多语言运行时。
WASM 组件模型:Rust 生态对 WebAssembly 的支持非常成熟。Bun 可能会更好地支持 WASM 组件,成为真正的前端+后端统一运行时。
更好的调试工具:Rust 生态中的调试和分析工具更加丰富,这可能带来更好的开发者体验。
Zig 的未来
Zig 不会因为 Bun 的迁移而消亡。它仍然有独特的价值:
- 作为 C 的替代者,在嵌入式和操作系统开发中有优势
- 编译速度极快,适合需要快速迭代的场景
comptime能力在某些领域无可替代
但 Zig 需要正视生态系统的差距,以及 AI 时代社区政策对人才吸引力的影响。
AI 辅助编程的新范式
Bun 的 6 天迁移预示着一种新的软件工程范式:
传统模式:需求 → 设计 → 编码 → 测试 → 部署(人驱动每一步)
新模式:需求 → 设计(人) → 编码(AI + 人审查) → 测试(自动化) → 部署(自动化)
在这个新模式中,人类的角色从"写代码"转变为"定义问题、设计架构、审查质量"。这不是程序员的终结,而是程序员价值的重新定位。
总结
Bun 从 Zig 到 Rust 的迁移,表面上是一个语言选择的故事,实际上揭示了 2026 年软件工程的三个深层变化:
系统编程语言格局已经明朗:Rust 成为主流选择,Zig 在特定领域有价值但生态差距短期内难以弥补。选择语言不仅是技术决策,更是生态和人才决策。
AI 辅助编程已经跨越临界点:96 万行代码 6 天迁移,这不是营销噱头,而是真实的生产力变革。但 AI 不是万能的——架构决策、边界情况、安全性审查仍然需要人类深度参与。
社区政策影响项目生命力:Zig 的反 AI 贡献政策与 Bun 大规模使用 AI 形成鲜明对比。在 AI 时代,社区对新技术工具的态度将直接影响项目吸引人才的能力。
对于普通开发者,我的建议很简单:学 Rust,用 AI,写测试。这不是跟风,而是在变化中抓住最确定的趋势。
Bun 的故事还在继续。无论它最终能否在 Rust 上重现甚至超越 Zig 版本的辉煌,这 6 天的迁移已经写进了软件工程的历史。它告诉我们:当技术决策、AI 工具和工程方法论三者在正确的方向上对齐时,"不可能"只是一个还没有尝试的词。