Software Engineering: TypeScript + Bun for Development… But Wait, It’s Built in Zig?
I’ve been working on vibebrowser.app recently, and like most modern JavaScript projects, I naturally reached for TypeScript for our…
Author: Dzianis Vashchuk | Site: Medium | Published: 2025-12-22T07:52:15Z
Software Engineering: TypeScript + Bun for Development… But Wait, It’s Built in Zig? I’ve been working on vibebrowser.app recently, and like most modern JavaScript projects, I naturally reached …
I’ve been working on vibebrowser.app recently, and like most modern JavaScript projects, I naturally reached for TypeScript for our development scripts. Speed and type safety — both essential when building tooling.Then I got curious: what is Bun actually built with? I expected Rust. Everyone builds everything in Rust these days, right?Nope. It’s Zig.That surprised me, and honestly, it made me appreciate the engineering decision behind it.The Initial AssumptionIf you’d asked me six months ago, I would’ve confidently said: “High-performance systems software? Obviously Rust.” It’s the safe answer. Rust dominates that space, and for good reason — compile-time memory safety guarantees are genuinely valuable.But Jarred Sumner and the Bun team made a different call, and after digging into why, I think there’s something worth understanding here.Why Zig Over Rust?1. Explicit > Implicit: The Borrow Checker ProblemRust’s borrow checker is powerful but opinionated. With a project as complex as a JavaScript runtime, you’re constantly negotiating with the compiler about ownership and lifetimes.Here’s the thing: Rust enforces that at any given time, you can either have one mutable reference or multiple immutable references to a piece of data. It’s preventing data races at compile time, which is great for safety. But for large systems projects, this gets painful fast.You end up spending time on borrow semantics instead of the actual problem: how do I efficiently parse JavaScript and manage heap allocations? You’re fighting the compiler’s ownership rules, refactoring to satisfy the borrow checker, restructuring data to pass the borrow rules. It’s safety, but it costs velocity.Example: Rust’s frictionstruct Parser { tokens: Vec<Token>,}impl Parser { fn parse(&mut self) -> Result<AST> { let token = &self.tokens[0]; let next = &self.tokens[1]; self.advance(); }}Same logic in Zig: straightforwardpub fn parse(self: *Parser) !AST { var token = self.tokens[0]; var next = self.tokens[1]; self.advance(); return buildAST(token, next);}Zig doesn’t prevent you from shooting yourself — it just makes the cost visible. For experienced developers on a large codebase, that’s often faster.For Bun, that trade-off made sense: give experienced devs the tools and get out of the way.2. Interoperability: C FFI Done RightJavaScript runtimes need to call native libraries constantly. Zig was designed with C interop as a first-class citizen. Rust can do this too, but it requires more ceremony.Rust’s FFI (requires boilerplate)#[link(name = "v8")]extern "C" { fn v8_create_isolate() -> *mut Isolate; fn v8_run_script(isolate: *mut Isolate, code: *const u8) -> *mut Value;} unsafe { let isolate = v8_create_isolate(); let result = v8_run_script(isolate, script_ptr); }Zig’s FFI (minimal friction)const v8 = @cImport({ @cInclude("v8.h");});pub fn runScript(code: []const u8) !Value { var isolate = v8.v8_create_isolate(); var result = v8.v8_run_script(isolate, code.ptr); return result;}Zig’s @cImport directly includes C headers. No bindings layer, no wrapper types — just use it. For a runtime that lives at the C boundary, that's huge.For a systems project, that matters.3. Developer Velocity: Iteration Speed MattersFor a single creator or small team building something complex, iteration speed is real. Zig’s straightforwardness means fewer compiler friction moments. You write what you mean, the compiler does what you say — no surprises.Rust: More back-and-forth with compilerfn process_tokens(tokens: &Vec<Token>) -> Result<Vec<AST>> { let mut results = Vec::new(); for token in tokens.iter() { let ast = parse_token(token)?; results.push(ast); } Ok(results)}Zig: Write it, run itpub fn processTokens(allocator: Allocator, tokens: []Token) ![]AST { var results = ArrayList(AST).init(allocator); for (tokens) |token| { var ast = try parseToken(&token); try results.append(ast); } return results.items;}With Zig, you write the algorithm, run it, and if there’s a memory issue, you debug it. With Rust, you’re often restructuring code before you can even compile. For rapid iteration on complex logic, that’s exhausting.You write what you mean, the compiler does what you say — no surprises.4. The Radical Simplicity: Make the Cost ExplicitThere’s something elegant about Zig’s philosophy: give you control, make the cost explicit, get out of your way. It’s almost anti-hype in an industry obsessed with safety guarantees.Rust: Safety baked infn allocate_buffer(size: usize) -> Result<Vec<u8>> { let buffer = vec![0u8; size]; Ok(buffer) }Zig: Explicit costs, full controlpub fn allocateBuffer(allocator: Allocator, size: usize) ![]u8 { var buffer = try allocator.alloc(u8, size); defer allocator.free(buffer); return buffer;}In Zig, there’s no “magic.” If something leaks, you did it. If performance is bad, you see why. In Rust, the compiler prevents entire categories of bugs — but it also prevents you from certain design patterns even if you know exactly what you’re doing.For experienced systems engineers building Bun, that transparency won the day.What This Means for MeI’m not switching everything to Zig tomorrow (and I probably don’t need to). But it reframed how I think about language choices. The right tool isn’t always the most popular tool.For vibebrowser’s development scripts and tooling, TypeScript + Bun is the sweet spot right now. But knowing it’s powered by Zig underneath? That makes the choice feel even better. It’s fast because someone deliberately chose a different path than the obvious one.The TakeawaySometimes the most interesting engineering decisions are the ones that go against the grain. Rust is genuinely excellent, but it’s not the only answer for systems programming. Zig’s bet on simplicity and explicitness is quietly proving itself through projects like Bun.If you’re building performance-critical tooling, it’s worth asking: what problem am I actually solving? The answer might not be the language everyone assumes.Have you used Zig or Bun? Curious to hear your take.Building vibebrowser.app with modern tooling and appreciating the thoughtful choices underneath. 🚀