Beans is a compiled language for backend services and business systems. I have been building it for a while. This post covers the two things I care about most: how it stays safe, and how it stays fast.

Safety: failure is part of the type

Beans has no null and no exceptions.

If a value can be missing, its type is Option<T>. If a function can fail, it returns Result<T>. You handle it with match, or pass it up with ?.

import std.io fn parse_qty(text: string) -> Result<int> { let qty: int = text.to_int()? if qty <= 0 { return err("quantity must be positive, got {qty}") } return ok(qty) } fn main() { match parse_qty("abc") { ok(qty) => { io.println("qty {qty}") } err(e) => { io.println("rejected: {e}") } } } 

This prints rejected: can't read 'abc' as int.

The part I like most is what happens when you forget. If you try to use the result as a plain number:

let qty: int = parse_qty("5") 

the compiler stops you:

error: expected int, got Result<int> 

No crash at 3 AM. The mistake shows up at build time.

Raw pointers and raw memory exist, because Beans is also for systems work. But they only work inside unsafe blocks, so you can find every risky line with one search.

Performance: native code, predictable memory

  • Native binaries through LLVM. beansc build app.b -o app gives you one executable.
  • Reference counting, not a tracing GC. Memory is freed when the last reference goes away. A cycle collector cleans up loops. There are no stop-the-world pauses.
  • Ownership control when you need it. move hands a value over without a copy. Shared<T> and Weak<T> make sharing explicit.
  • Fibers and threads. Fibers for cheap concurrency, plus threads, channels, mutexes, and typed atomics.

I do not publish speed numbers yet. The benchmark suite runs each workload against tuned C++ references, with fixed checksums and a 3% variance limit. A result only counts if it passes on both Linux x86-64 and macOS ARM64. When it does, I will post the numbers, good or bad.

Two engines, one answer

beansc run runs code in an interpreter, so iteration is fast. beansc build makes the native binary. The test suite runs programs both ways and checks they give the same output. It also checks that the compiler, which is written in Beans, rebuilds itself byte for byte.

What you can build today

A web API with Espresso: controllers, constructor injection, middleware, OpenAPI, on a non-blocking HTTP/1.1 event loop.

import github.com/beans-lang/espresso @espresso.controller(route: "/hello") pub class HelloController extends espresso.Controller { pub fn init() {} @espresso.get(route: r"/{name}") pub fn hello(@espresso.route name: string) -> Result<espresso.ActionResult> { return self.ok_text("Hello, {name}!") } } 

Even here, the action returns a Result. Errors stay values all the way up.

Also in the ecosystem:

  • Latte: server-rendered UI components that can also run in the browser as WebAssembly.
  • Cortado: native desktop apps.
  • Drivers for PostgreSQL, MySQL, SQLite, and Redis.

Where it stands

Beans is pre-1.0 (v0.1.52). Things can still change. I am looking for people to build a small real tool with it and tell me what breaks.

If you try it, I would like to hear what felt wrong.