Traits Arrive in Luma - A Big Step Forward

Traits Arrive in Luma - A Big Step Forward

December 9, 2025·
Luma Core Team

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 self in 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 += dx

did 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 = alice

the compiler quietly transforms it to:

var g Greetable = &alice

This 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:

  • filter
  • walk
  • 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 (self vs mut 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.

Last updated on