编程 Bun 六天重写:96万行代码从 Zig 迁移到 Rust,系统编程语言王座易主的信号

2026-05-15 10:44:59 +0800 CST views 3

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 团队采用的是"行为等价重写"策略:

  1. 以测试套件为契约:Bun 已有的测试套件是迁移的锚点。只要新代码能通过相同的测试,就认为行为等价。
  2. AI 辅助生成骨架:利用 Claude Code 生成 Rust 代码骨架,然后人工审查和调整。
  3. 增量验证:按模块逐步迁移,每个模块迁移完立即跑测试,确保不偏离。
  4. 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 版 BunRust 版 Bun说明
启动时间~5ms预计 ~5ms两者都是原生二进制,差异不大
HTTP 吞吐量~300K req/s预计 ~350K+ req/stokio 的成熟调度器可能更优
内存占用随时间增长(泄漏)稳定Rust 所有权模型杜绝泄漏
npm install~2s (冷启动)预计 ~2sI/O 密集型,语言差异不大
FFI 开销无(Zig 天然兼容 C)有(Rust FFI 有少量开销)Zig 的 C 互操作更直接
编译速度快(Zig 编译快)慢(Rust 编译慢)开发体验的trade-off

值得注意的是,Rust 的编译速度确实是个痛点。Zig 的编译速度是它的显著优势之一,而 Rust 的编译时间在大型项目中可能让人抓狂。不过,对于运行时性能而言,编译速度并不影响。

未来展望

Bun 的 Rust 未来

Rust 版 Bun 带来了几个新的可能性:

  1. 执行 Java 字节码:Jarred 在公告中提到,新的 Rust 版本具备了执行 Java 的能力。这意味着 Bun 可能从一个 JavaScript 运行时演变为一个多语言运行时。

  2. WASM 组件模型:Rust 生态对 WebAssembly 的支持非常成熟。Bun 可能会更好地支持 WASM 组件,成为真正的前端+后端统一运行时。

  3. 更好的调试工具:Rust 生态中的调试和分析工具更加丰富,这可能带来更好的开发者体验。

Zig 的未来

Zig 不会因为 Bun 的迁移而消亡。它仍然有独特的价值:

  • 作为 C 的替代者,在嵌入式和操作系统开发中有优势
  • 编译速度极快,适合需要快速迭代的场景
  • comptime 能力在某些领域无可替代

但 Zig 需要正视生态系统的差距,以及 AI 时代社区政策对人才吸引力的影响。

AI 辅助编程的新范式

Bun 的 6 天迁移预示着一种新的软件工程范式:

传统模式:需求 → 设计 → 编码 → 测试 → 部署(人驱动每一步)

新模式:需求 → 设计(人) → 编码(AI + 人审查) → 测试(自动化) → 部署(自动化)

在这个新模式中,人类的角色从"写代码"转变为"定义问题、设计架构、审查质量"。这不是程序员的终结,而是程序员价值的重新定位。

总结

Bun 从 Zig 到 Rust 的迁移,表面上是一个语言选择的故事,实际上揭示了 2026 年软件工程的三个深层变化:

  1. 系统编程语言格局已经明朗:Rust 成为主流选择,Zig 在特定领域有价值但生态差距短期内难以弥补。选择语言不仅是技术决策,更是生态和人才决策。

  2. AI 辅助编程已经跨越临界点:96 万行代码 6 天迁移,这不是营销噱头,而是真实的生产力变革。但 AI 不是万能的——架构决策、边界情况、安全性审查仍然需要人类深度参与。

  3. 社区政策影响项目生命力:Zig 的反 AI 贡献政策与 Bun 大规模使用 AI 形成鲜明对比。在 AI 时代,社区对新技术工具的态度将直接影响项目吸引人才的能力。

对于普通开发者,我的建议很简单:学 Rust,用 AI,写测试。这不是跟风,而是在变化中抓住最确定的趋势。

Bun 的故事还在继续。无论它最终能否在 Rust 上重现甚至超越 Zig 版本的辉煌,这 6 天的迁移已经写进了软件工程的历史。它告诉我们:当技术决策、AI 工具和工程方法论三者在正确的方向上对齐时,"不可能"只是一个还没有尝试的词。

推荐文章

PHP 命令行模式后台执行指南
2025-05-14 10:05:31 +0800 CST
Grid布局的简洁性和高效性
2024-11-18 03:48:02 +0800 CST
vue打包后如何进行调试错误
2024-11-17 18:20:37 +0800 CST
百度开源压测工具 dperf
2024-11-18 16:50:58 +0800 CST
html一些比较人使用的技巧和代码
2024-11-17 05:05:01 +0800 CST
15 个你应该了解的有用 CSS 属性
2024-11-18 15:24:50 +0800 CST
Graphene:一个无敌的 Python 库!
2024-11-19 04:32:49 +0800 CST
使用Python提取图片中的GPS信息
2024-11-18 13:46:22 +0800 CST
程序员茄子在线接单