Traits Arrive in Luma - A Big Step Forward
Over the last weeks, Luma has gone through one of its largest language updates so far: a complete traits system with dynamic dispatch, mutating methods, and seamless integration into lists, maps, and function calls.
This post is a look behind the scenes - what we built, why we built it, what challenges we ran into, and how this update makes Luma feel more natural and expressive.
What Are Traits, and Why Do They Matter?
Traits let you describe capabilities independent of concrete types.
A simple example:
trait Greetable {
fn greet(self) -> str
}
struct Person { name: str }
struct Robot { id: int }
impl Greetable for Person { ... }
impl Greetable for Robot { ... }Now both Person and Robot can be used wherever something “greetable” is required:
g: Greetable = alice
print(g.greet())
g = r2
print(g.greet())This is dynamic dispatch - the correct method is chosen at runtime.
Traits give Luma flexibility traditionally found in languages like Rust, Swift, or Go’s interfaces, while keeping Luma’s clean, minimal syntax.
What This Update Adds to Luma
Here’s what the new trait system now supports:
✔ Trait declarations
With methods, parameters, and return types.
✔ Trait implementations
Concrete types can implement one or many traits.
✔ Dynamic dispatch
Trait-typed variables now call the correct method based on the actual value they contain.
✔ Traits inside lists and maps
Lists of trait objects work naturally:
items: [Greetable] = [alice, r2]
items.walk(x -> print(x.greet()))✔ Mutating trait methods
Trait methods like:
fn move(self, dx: float, dy: float)can safely mutate the underlying struct.
✔ Pointer semantics - automatic, invisible
This was the biggest and most important decision in the update:
Luma now automatically treats
selfin trait and impl methods as mutable.
Under the hood, the compiler generates Go methods using pointer receivers.
In user code, nothing changes - mutation “just works”.
What Was the Hard Part?
A few things turned out to be trickier than expected. Here’s a peek into the challenges.
1. Mutating self required pointer semantics
Luma structs behave like value types. But mutating a value type inside a method doesn’t update the original.
So this:
self.x += dxdid nothing.
The compiler now automatically converts:
- every impl method
- every trait method implementation
into a pointer-receiver function under the hood.
From the user’s perspective:
- cleaner language
- correct semantics
- no unsafe magic
Dynamic dispatch required adapter methods
When you assign a struct to a trait variable:
g: Greetable = alicethe compiler quietly transforms it to:
var g Greetable = &aliceThis allows trait calls like g.greet() to work consistently, regardless of the concrete type. This required creating tiny “adapter methods” in Go, automatically generated by the compiler.
3. Lists and maps needed type-aware construction
Typed lists like:
nums: [int] = [1, 2, 3]were initially compiled to:
[]interface{}{1, 2, 3}- breaking all typed list operations.
The compiler now uses type context (“hints”) to emit correct typed literals:
[]int{1, 2, 3}The same applies to:
[?int][map[str]int]map[str]Person- and trait lists like
[Greetable].
This was a major upgrade for overall type correctness.
Lambda support
Luma’s lambdas (x -> x > 2) now compile into clean, typed Go function literals when used in:
filterwalk- higher-order functions
- trait calls
This required the compiler to infer parameter and return types from context.
Why This Matters for Developers
With this update, Luma now supports:
- Modular design through traits
- Polymorphic programming
- Clean abstractions
- Mutating methods on structs
- Typed higher-order functions
- Type-safe collection manipulation
Most of all, it gets us closer to the language Luma is meant to be:
A small, elegant, expressive language that feels like a blend of Go, Rust, and Python, without the complexity.
Traits bring structure and power without noise. This is one of those updates that quietly unlocks an entire category of programming patterns.
What’s Next?
We already see the natural next steps:
- Default trait methods
- Trait bounds for generics
- Auto-derive traits (e.g.
Show,Clone) - Protocol-oriented patterns
- Immutable & mutable receivers (
selfvsmut self)
But those will come in stages - for now, we’re enjoying the solid foundation traits bring.
Try It Out
Traits are available in the Luma compiler now. Write a file, run luma run file.luma and play with the new features.
We’re excited to see how the community uses traits in real programs.