· 6 min read ·

Rust's Syntax Was Always Separable From Its Borrow Checker

Source: lobsters

There is a pattern that keeps recurring in language design: take a runtime that people trust, attach a syntax that people prefer, and ship something in between. Elixir did it to the BEAM. Kotlin did it to the JVM. Scala did it to the JVM in a different direction. Now Lisette is doing it to the Go runtime, and the pairing is worth thinking through carefully because it illuminates something specific about what makes both Rust and Go appealing in the first place.

The pitch is direct: Rust syntax, Go runtime. That’s the whole proposition. What it implies is that these two things are separable, which they are, but the separation has costs that depend heavily on which parts of Rust you think matter.

What the Go Runtime Actually Provides

The Go runtime is not just a garbage collector. It is an M:N threading scheduler, a channel implementation, a memory allocator, and a GC, all integrated into a single coherent system that has been in production at Google and across the industry for over a decade.

Goroutines start at around 2KB of stack space, grow on demand, and are multiplexed across OS threads by the Go scheduler using work-stealing. This is the mechanism that lets Go programs run hundreds of thousands of concurrent tasks without the overhead of OS threads, which typically consume 1-8MB each. The scheduler cooperates with the GC: when the GC needs to run, it can stop goroutines at safe points rather than requiring stop-the-world pauses across the board.

Go’s GC targets sub-millisecond pause times in practice. It uses a tri-color concurrent mark-and-sweep algorithm that does most of its work concurrently with the program. The pause times are not zero, but they are low enough that Go runs comfortably in latency-sensitive server workloads where Java’s GC historically struggled.

Channels give you CSP-style communication between goroutines: typed, buffered or unbuffered, with select for multiplexing. This model maps well onto networked services, which is the primary domain Go was designed for.

So when Lisette says “Go runtime,” it means all of that: the scheduler, the GC, the channel semantics, the standard library networking stack. That is a lot to get for free.

What Rust’s Syntax Gives You Without the Borrow Checker

This is the question the language is implicitly answering, and it is more interesting than it sounds.

Rust’s ownership system and its syntax are distinct things. The borrow checker enforces ownership rules at compile time. The syntax, independently, gives you algebraic data types with associated data, exhaustive pattern matching, Result and Option as first-class types rather than conventions, method syntax via impl blocks, trait-based polymorphism, type inference that extends through complex expressions, and closures with explicit capture semantics.

None of those features require a borrow checker. They require a type system capable of expressing them, which Go’s type system historically has not been. Go’s generics, added in 1.18, helped, but Go still does not have sum types. You cannot write:

type Shape =
    | Circle { radius: f64 }
    | Rectangle { width: f64, height: f64 }

In Go, the idiomatic equivalent involves interfaces and type switches or type assertions, which are not exhaustively checked by the compiler. You can add a new Shape variant and forget to handle it, and the compiler will not tell you.

With Rust-style syntax, a pattern match over a sum type is exhaustive by default:

match shape {
    Shape::Circle { radius } => area_circle(radius),
    Shape::Rectangle { width, height } => width * height,
}

If you add a new variant and forget to update the match, the compiler errors. That is not a borrow checker feature. That is a type system feature that Rust syntax brings, and it is legitimately valuable for writing correct programs.

Similarly, replacing nil-returning functions with Result<T, E> and Option<T> does not require ownership semantics. It requires the language to have sum types and pattern matching. Rust has these because they are necessary for expressing ownership idioms cleanly, but they are useful on their own.

The Trade-Off Being Made

When you put Rust syntax on Go’s runtime, you are making a specific bet: that the ergonomics of Rust’s type system are worth having, but the compile-time memory safety guarantees that the borrow checker provides are not the priority.

This is a defensible position for a large class of programs. Networked services written in Go are not typically constrained by GC overhead. The allocation patterns in a web server or a message queue are predictable enough that Go’s GC handles them gracefully. In these workloads, the programming model matters more than zero-cost abstractions.

What you give up is deterministic destruction and the aliasing guarantees the borrow checker provides. You cannot write code that the compiler proves is free of data races. Go does have a runtime race detector (-race flag), but it is probabilistic and adds overhead; it is a debugging tool, not a correctness guarantee. Rust’s ownership system eliminates the class of bugs the race detector is looking for, at compile time, for free at runtime.

You also lose RAII in the strong sense. In Rust, the Drop trait gives you deterministic cleanup tied to scope. In a GC language, resource cleanup has to be explicit (via defer in Go’s idiom) because the GC does not run destructors at predictable times. This matters for file handles, network connections, locks, and any resource that needs prompt cleanup.

Why This Approach Has Precedent

The BEAM community figured out a version of this years ago. Elixir and Gleam both run on the Erlang virtual machine, which has its own preemptive scheduler and per-process GC. Elixir brought a Ruby-influenced syntax that many developers found more approachable than Erlang’s Prolog-derived notation. Gleam brought ML-influenced types with exhaustive pattern matching and type inference, without Erlang’s dynamic typing.

Both languages are successful because the BEAM’s runtime properties (fault tolerance, hot code reloading, distribution) are genuinely hard to replicate, and attaching a better surface syntax to a proven runtime is a reasonable division of labor.

Go’s runtime is not the BEAM, but it has analogous properties in its domain: the goroutine scheduler and GC are hard to build, have been tuned for years in production, and represent real engineering. Starting from them is not laziness; it is pragmatism.

What the Lobsters Crowd Will Debate

The Lisette announcement surfaced on Lobsters, and the discussion will likely cluster around a few predictable axes. Some commenters will argue that the borrow checker is precisely the part that matters, that Rust syntax without ownership is aesthetic sugar on a GC language and you would be better served learning Go idioms. Others will argue that Go’s syntax is the actual barrier to adoption and that any improvement there is worthwhile.

Both positions contain truth. The borrow checker is not separable from Rust’s most interesting guarantees. But Go’s syntax does have real limitations, and sum types with exhaustive matching would make many Go programs cleaner and more correct.

The more interesting question is whether Lisette can deliver on the full promise of the syntax. Rust’s type system is coherent because ownership, lifetimes, and the trait system were designed together. Adopting the syntax while running on a different memory model means some idioms will not translate cleanly. What happens to iterators designed around lending semantics? What happens to trait objects when you have a GC? The answers to those questions are in the language’s specification and standard library design, and they will determine whether Lisette is a genuine synthesis or a collection of syntax borrowed from a language whose design it cannot fully replicate.

Language design in this space is not zero-sum. A language that makes concurrent networked services easier to write correctly, even without compile-time memory safety proofs, is useful. Whether Lisette clears that bar depends on execution, tooling, and ecosystem. The premise is at least coherent.

Was this interesting?