Impl Blocks (Methods)

Impl Blocks (Methods)

An impl block attaches methods and associated functions to a struct.

impl Person {

    // Associated function (static)
    fn new(name: str, age: int) -> Person {
        return Person { name: name, age: age }
    }

    // Instance method
    fn greet() -> str {
        return "Hello, ${self.name}!"
    }
}

Instance Methods

Inside methods, the special identifier self always refers to the current instance.

  • You do not declare it
  • You do not list it in the parameter list
  • It is implicitly available inside any method in impl
fn greet() -> str {
    return "Hello, ${self.name}!"
}

Mutating self

Methods may freely mutate fields:

fn birthday() {
    self.age += 1
}

self acts as a mutable reference to the instance.

Calling methods

bob: Person = Person.new("Bob", 30)
print(bob.greet())

Method Dispatch Rules (Important)

Luma uses simple, predictable rules:

Instance type → uses inherent methods (impl Struct)

alice: Person
alice.greet()     // uses Person’s own greet() method

Trait type → uses trait implementation (impl Trait for Struct)

person: Greetable = alice
person.greet()    // uses Greetable’s greet() implementation

This is the same model used by Go interfaces and Rust traits.

Full Example

struct Vector2 { 
    x: float, 
    y: float 
}

trait Movable {
    fn move(self, dx: float, dy: float)
}

impl Movable for Vector2 {
    fn move(self, dx: float, dy: float) {
        self.x += dx
        self.y += dy
    }
}

// Usage
pos: Vector2 = Vector2 { x: 0.0, y: 0.0 }
pos.move(1.5, 2.0)
print(pos)  // Vector2 { x: 1.5, y: 2.0 }

Why Structs & Traits (Not Classes)?

Luma avoids classes intentionally:

  • No inheritance chains
  • No hidden magic
  • No subclass surprises
  • Behavior is granular and composable
  • Data stays separate from logic
  • More consistent with functions, lambdas, and traits

This gives clearer programs and fewer surprises for new developers.

Last updated on