Lisette is a new language that takes a specific bet: the Go runtime is good, and Rust syntax is better than Go syntax, so combine them. It targets the Go scheduler (M:N green threads, goroutines, channels) and Go’s tricolor mark-and-sweep garbage collector, while surfacing algebraic data types, pattern matching, Result/Option, traits, generics, and fn/let/match ergonomics at the language level. No borrow checker. No lifetime annotations.
That combination is worth taking seriously on its own terms, because it forces a question most language designers sidestep: which choice matters more, the runtime model or the surface syntax?
What “Rust Syntax” Means Without Ownership
Rust’s syntax is not merely aesthetic. A lot of it exists to serve the borrow checker. Lifetimes, &, &mut, Box<T>, Rc<T>, Arc<T>, the move semantics scaffolding around closures, the distinction between String and &str at the type level: these are all load-bearing parts of a system that enforces memory safety at compile time without a GC. Strip the borrow checker and a significant fraction of Rust’s complexity evaporates.
What remains is genuinely ergonomic. Sum types (enum with data) and exhaustive match expressions eliminate whole categories of runtime errors that Go programs handle poorly. Option<T> in place of Go’s nil pointers makes the absence of a value explicit at the type level. Result<T, E> with ? propagation is cleaner than Go’s if err != nil chains, which are syntactically cheap but semantically noisy. Traits give you principled polymorphism without the implicit interface satisfaction that sometimes makes Go’s interface resolution confusing to follow.
Lisette takes this surface and drops it onto a GC’d runtime. Memory is managed. Closures can capture freely. You do not reason about who owns a value. This is not a compromise: it is a deliberate reframing of what Rust-style syntax is actually for.
What the Go Runtime Provides
Go’s runtime has compounded over fifteen years of production use. Goroutines are cheap to spawn (initial stack around 2 KB, growing as needed), and the M:N scheduler multiplexes them across OS threads without requiring the programmer to manage a thread pool. Channels give you a typed, blocking communication primitive that composes naturally with select. The standard library is wide, stable, and well-documented. Compilation is fast, to a degree that Rust users find almost unsettling.
The GC is not a trade-off to apologize for. It is a tricolor concurrent mark-and-sweep collector with low pause times, and it has been tuned aggressively over successive releases. For network services and CLI tools, it is rarely the bottleneck. The conventional wisdom that GC’d languages are inappropriate for systems work has eroded considerably; the question is whether the specific pause and throughput characteristics fit the workload.
Lisette inherits all of this. The concurrency story is Go’s concurrency story, and that story is one of the strongest in mainstream languages.
The Pain Points This Addresses
Go developers who have spent time with Rust or Haskell or even TypeScript develop a specific list of frustrations. Nil pointer dereferences at runtime that an Option type would have caught at compile time. Error handling that requires discipline to not ignore, but provides no syntax that makes ignoring it a compile error. Interface satisfaction that is implicit and occasionally surprising. The absence of sum types that leads to struct-embedding patterns and boolean flag fields to represent variants.
These are not hypothetical complaints. Go’s standard library ships error as a bare interface, returns it alongside values, and trusts the programmer to check it. The language gives you no help when you forget. Adding Option and Result as first-class types with exhaustive pattern matching on the variants addresses this at the source. It is not the only approach, but it is a proven one.
The Pain Points This Avoids
Rust’s learning curve is real and concentrated in a specific place. The borrow checker is conceptually coherent once you internalize it, but the path to internalization is long for programmers coming from GC’d languages. Lifetime annotations in generic code compound the cognitive load. Compile times, while improving, remain substantially longer than Go for large projects. The async ecosystem added another layer of complexity, the Pin, Future, executor question, that goroutines sidestep entirely.
Lisette sidesteps all of this. GC handles memory. Goroutines handle concurrency. You write Rust-flavored types and let the runtime do the rest.
A Concrete Look at the Syntax Difference
Consider a goroutine-style worker pattern. In Go:
func worker(jobs <-chan int, results chan<- int) {
for j := range jobs {
result, err := process(j)
if err != nil {
log.Printf("error: %v", err)
continue
}
results <- result
}
}
In equivalent Lisette-style syntax, representative of the described design:
fn worker(jobs: Receiver<i64>, results: Sender<i64>) {
for j in jobs {
match process(j) {
Ok(result) => results.send(result),
Err(e) => log::warn!("error: {}", e),
}
}
}
And in Rust proper, where there are no goroutines and async or OS threads are required:
async fn worker(
mut jobs: mpsc::Receiver<i64>,
results: mpsc::Sender<i64>,
) {
while let Some(j) = jobs.recv().await {
match process(j).await {
Ok(result) => { let _ = results.send(result).await; }
Err(e) => tracing::warn!("error: {}", e),
}
}
}
The Lisette version gets the ergonomic wins from Rust (match on Result, typed channels, fn syntax) without the async machinery that Rust requires because it lacks a built-in scheduler. The Go version is readable, but the error path is imperative and easy to accidentally skip.
Prior Art: When This Has Been Done Before
The pattern of decoupling syntax from runtime is not new. Gleam runs on the BEAM VM, giving it Erlang’s actor model and preemptive scheduler while offering an ML-style type system with full type inference, sum types, and pattern matching. Gleam’s bet is similar to Lisette’s: the BEAM is an excellent runtime for fault-tolerant concurrent systems, and a better type system on top of it is worth having. The result has attracted meaningful adoption in the Erlang and Elixir ecosystem.
Kotlin on the JVM is a longer-running example. Kotlin took Java’s runtime and replaced the syntax and type system with something substantially more ergonomic: null safety baked into the type system, data classes, sealed classes as sum types, coroutines as a library. The JVM underneath did not change. The programming model on top changed considerably, and the migration story from Java was smoother than any rewrite would have been.
F# runs on the .NET CLR, gives you OCaml-flavored syntax with pattern matching and discriminated unions, and exposes the full .NET ecosystem. It coexists with C# on the same runtime, which means interop is natural and the runtime investment is shared. Lisette’s relationship to Go is structurally analogous.
Languages like Zig and Hare made the opposite bet: they took C’s runtime model (manual memory, no GC, close to the metal) and improved the syntax and safety story without adding a garbage collector. Vale explored a third path, trying to get memory safety without GC and without a borrow checker through generational references. Each of these is a different resolution to the same underlying question about which axis of the design space matters most.
The Fundamental Question
The philosophical split in language design is between runtime-first and syntax-first thinking. Runtime-first means choosing your execution model, then building the best syntax for it. Syntax-first means choosing the type system and ergonomics you want, then finding or building a runtime that fits.
Most successful languages have been runtime-first. C was designed around direct hardware execution. Java was designed around the JVM’s portability model. Go was designed around a specific concurrency thesis, and its syntax reflects that: simple, oriented toward readability, with concurrency primitives built into the language. The syntax followed from the runtime constraints.
Lisette is syntax-first. It starts from the ergonomics of Rust, minus the parts that only exist to serve the borrow checker, and routes them through a runtime that was already well understood. This is a coherent strategy. The Go runtime is not a legacy constraint; it is a well-engineered piece of infrastructure with a large ecosystem and a well-understood operational profile.
The risk is the gap between the syntax’s implications and the runtime’s behavior. Rust syntax primes programmers to think about ownership and borrowing. Lisette’s version of that syntax does not enforce those properties. A programmer reading Lisette code who has internalized Rust may make assumptions that do not hold. A programmer reading Lisette code who has internalized Go may find the syntax unfamiliar in ways that slow comprehension. The language occupies a middle ground that is genuinely novel, and novelty has costs alongside its benefits.
Whether those costs are worth the ergonomic gains depends on what you are building and who is building it. For Go teams who have repeatedly wished for sum types and better error handling ergonomics, Lisette’s value proposition is clear. For Rust teams looking for a faster, GC’d alternative, it is more complicated: the syntax is familiar but the runtime guarantees are fundamentally different. The bet Lisette makes is that Go’s runtime is good enough that better syntax is the remaining constraint, and that is a bet that deserves a careful look.