New Mojo language feature: default trait methods!

We unveiled this live at the Modular community meeting last night: default methods can now be defined on traits! This lets a trait define an implementation of a method that is provided to all conforming structs. These default methods can be explicitly overridden in conforming structs, as needed.

As an example:

# Any struct conforming to EqualityComparable now only needs to define one of
# __ne__ ~or~ __eq__ and will get a definition of the other with no
# additional code!

# For instance:
trait EqualityComparable:
    fn __eq__(self, other: Self) -> Bool:
        ...

    fn __ne__(self, other: Self) -> Bool:
        return not self.__eq__(other)

@value
struct Point(EqualityComparable):
    var x: Int
    var y: Int

    fn __eq__(self, other: Self) -> Bool:
        # Since __eq__ is implemented we now get __ne__ defined for free!
        return self.x == other.x and self.y == other.y

    # Defaulted methods can also be overriden if we want different behavior.
    # fn __ne__(self, other: Self) -> Bool:
    #     return self.x != other.x or self.y != other.y

Currently a trait method is considered to be non-defaulted if the first thing in it’s body is either a ‘…’ or a ‘pass’. In the future, only ‘…’ will mark a trait method as not defaulted. For example:


trait Foo:
  # Either of the following are non-defaulted
  # fn foo(self):
  #   ...
  #
  # fn foo(self):
  #   pass

  # While this is not:
  fn foo(self):
    print("Foo.foo")

We’re excited to see how much code we can clean up as a result of this new feature. Check out all that red in this commit due to being able to define __ne__ in terms of __eq__ within EqualityComparable. And that’s just one case!

7 Likes

I’ve been using this feature a lot lately, for what it’s worth. It was nice to remove a lot of boilerplate code by switching to default trait methods! :+1:

2 Likes

Hi @BradLarson! The code below emits error async defaulted trait methods are not supported yet. Any idea when this will be resolved? Thanks!

trait Watchable:
    async fn wd_run(
        mut self,
        mut wd_system: WdSystem,
        interval: UInt = 0,
        duration: UInt = 0,
        iterations: UInt = 0,
    ):
        with wd_system.task_block(interval, duration, iterations) as task_id:
            while wd_system.set_running(task_id, True):
                self._run()

    fn _run(mut self):
        ...

As we talk about on the Mojo roadmap, a robust async system will be a post-1.0 effort. We’re not focusing on these aspects right now, so we haven’t yet invested in intersections with async functions and some newer language features like this. It may be a little while before we’re able to build this out properly.

1 Like

I am working with the existing async capabilities available using TaskGroup, and so would like to define boilerplate code in a trait as shown in my example above. Although my request is straightforward…allow for async defaulted trait methods, I understand that implementing ‘simple’ can sometimes be onerous. I had hoped that this scenario would be a sooner rather than later possibility. Thanks!