When self Stopped Getting in the Way

When self Stopped Getting in the Way

January 26, 2026·
Luma Core Team

Language design is often about noticing friction in places we’ve already accepted as “normal”. You write something the same way a hundred times, and only later realize that the language has been asking you to repeat information it already knows.

That’s what happened with self in Luma.

For a long time, instance methods required an explicit self parameter. It worked. It was clear. And yet, it always felt slightly off — especially in a language that tries hard to let intent speak louder than ceremony.

The quiet awkwardness of repeating yourself

Consider a simple method on a struct:

impl Person {
    fn greet(self) -> str {
        return "Hello, ${self.name}!"
    }
}

There’s nothing wrong with this. But if you read it carefully, you’ll notice something redundant: the method is already inside impl Person. The language already knows this function belongs to Person. And yet, you still had to spell that relationship out again by declaring self.

In practice, this meant every instance method started with a small but unavoidable piece of boilerplate. Not a big problem — just enough to be noticeable.

Luma tries to avoid that kind of friction.

Letting the context do its job

The solution was not to invent new syntax or rules, but to trust the structure that already exists. Inside an impl block, the instance context is obvious. If a method refers to self, then it is clearly an instance method. If it doesn’t, then it’s just a function associated with the type.

With that in mind, Luma now allows self to be implicitly available inside instance methods.

impl Person {
    fn greet() -> str {
        return "Hello, ${self.name}!"
    }
}

Nothing magical is happening here from the user’s point of view. The method still behaves the same. You still access fields through self. But the signature is cleaner, and the intent is clearer. The method says what it does, not what it needs in order to do it.

Code that reads more like thought

This change becomes even more valuable as methods grow. When logic gets more complex, removing visual noise from the signature helps the reader focus on what matters.

impl Person {
    fn is_adult() -> bool {
        return self.age >= 18
    }

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

Nothing here needs explanation. The code reads naturally. self is available where it’s meaningful, and absent where it isn’t.

This also makes it easier to see the difference between instance methods and associated functions at a glance.

Instance methods vs associated functions

Not every function inside an impl block needs access to an instance. Some functions exist to construct or describe values, not to operate on a specific one.

Those functions remain unchanged.

impl Person {
    fn new(name: str, age: int) -> Person {
        return Person { name: name, age: age }
    }

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

Here, the distinction is clear without extra rules. new doesn’t use self, so it behaves like a factory function. greet does, so it’s an instance method. The language doesn’t need keywords or annotations to explain the difference — the code explains itself.

Traits benefit naturally

Because this change is about how methods are written, not about how they are called, it fits seamlessly with traits as well.

trait Greetable {
    fn greet() -> str
}

impl Greetable for Person {
    fn greet() -> str {
        return "Hi, I'm ${self.name}!"
    }
}

Nothing special needs to be learned. The same rules apply. If the method refers to self, it’s an instance method. If it doesn’t, it isn’t.

Why this matters

On its own, this is a small change. You could argue that writing self explicitly wasn’t a serious problem. But language design is rarely about single features in isolation. It’s about consistency, readability, and momentum.

Removing explicit self declarations:

  • makes method signatures easier to read
  • reduces boilerplate without hiding intent
  • keeps instance logic focused inside the method body
  • aligns Luma more closely with how people naturally think about objects

Most importantly, it reinforces a pattern that shows up across the language: when the context already provides the information, the user shouldn’t have to repeat it.

A step toward calmer code

This change didn’t add power to Luma. It removed a small obstacle. And those are often the most valuable improvements.

Code becomes quieter. Methods become easier to scan. The distinction between “what this method does” and “what this method needs” becomes sharper.

Implicit self is one of those features that, once added, immediately feels like it was always meant to be there.

And that’s usually how you know it’s the right move.

Last updated on