Traits were introduced in Mojo recently, so I thought I'd try them out. Currently, the built-in traits include , , , , , , and (looks like the "-able" suffix is a thing in these naming conventions!). Traits work on structs. To implement a trait, you simply add a method to a struct that matches the trait; then pass the trait name as a parameter: CollectionElement Copyable Destructable Intable Movable Sized Stringable @value struct Duck(Quackable): fn quack(self): print("Quack!") The decorator in Mojo inserts the lifecycle methods like , and into the struct, simplifying our life a bit as we don’t have to add them ourselves. @value __init__() __copyinit__() __moveinit__() Traits in Mojo don't support default implementations for methods yet, hence, the in the body of the method above. You can also use , which will have the same effect, as in Python. ... Quackable pass Mojo Traits vs. Go Interfaces Despite the different name, the basic approach to traits in Mojo reminds me of Go interfaces. In Go, you would define a struct and implement a interface like this: Duck Quackable type Quackable interface { quack() } And to make a struct that satisfies this interface: type Duck struct {} func (d Duck) quack() { fmt.Println("Quack!") } Compare it to a Mojo implementation: trait Quackable: fn quack(self): ... @value struct Duck(Quackable): fn quack(self): print("Quack!") I think the Mojo version is even more concise, nesting the method definition inside the struct type. In addition to working on structs, traits in Mojo don't require an keyword, just like in Go. quack() Duck implements Interestingly, the point is made in Mojo docs that default implementations are not allowed , meaning they might be allowed in the future. This means that Mojo might pursue an approach different from Go, which focuses on structs an interface, not it. yet satisfying implementing Default method implementations are not needed in Go, and a similar effect is achieved with embedding, as interface/trait implementation is simply a concept that doesn't exist in Go. Things might be different in Mojo. How to Use Mojo Traits The value of traits and interfaces is to make code reusable. For instance, in Mojo, you can write functions that accept trait types… fn make_it_quack[T: Quackable](could_be_duck: T): could_be_duck.quack() And then pass different structs that implement the same trait as input - everything will just work! For example, here's a struct that implements : Goose Quackable @value struct Goose(Quackable): fn quack(self): print("Honk!") And here, to call on both the and the (remember, you need the function in Mojo as an entry point to your program): make_it_quack Goose Duck main def main(): make_it_quack(Duck()) make_it_quack(Goose()) The output of this will be Quack! Honk! Trait Errors If I tried to pass in something that doesn't implement the trait to the function, let's say , the program won't compile: Quackable make_it_quack StealthCow @value struct StealthCow(): pass make_it_quack(StealthCow()) The error message below could have been more descriptive; maybe the Mojo team will improve it? error: invalid call to 'make_it_quack': callee expects 1 input parameter, but 0 were specified Same if I remove the method from ; here, we get a nice, descriptive error: quack Goose struct 'Goose' does not implement all requirements for 'Quackable' required function 'quack' is not implemented The advantage of this kind of static type checking over the pure Python approach is that we can catch errors easily and avoid shipping any mistakes to production as the code will simply not compile. Inheritance Traits in Mojo already support inheritance, so our `Quackable` trait can extend an `Audible` trait like so: trait Audible: fn make_sound(self): ... trait Quackable(Audible): fn quack(self): ... This means that a struct will have to implement both and to conform to the trait. Duck quack make_sound Quackable This is similar to the concept of "Interface embedding" in Go, where to make a new interface that inherits from other interfaces, you would embed parent interfaces like this: type Quackable interface { Audible // includes methods of Audible in Quackable's method set } Static Methods Traits also accept static methods that work without creating an instance of a struct: trait Swims: @staticmethod fn swim(): ... @value struct Duck(Quackable, Swims): fn quack(self): print("Quack!") @staticmethod fn swim(): print("Swimming") fn make_it_swim[T: Swims](): T.swim() You call a static method like this: def main(): make_it_quack(Duck()) make_it_quack(Goose()) Duck.swim() Which will output: Quack! Honk! Swimming Notice that in the last method call, a Duck instance is not created. This is how static methods in Python work, departing somewhat from object-oriented programming. Mojo builds on this Python functionality. Limitations of Mojo Traits Compared to Go Interfaces Interestingly, the Go trick with an empty that allows to pass any type and was popular with the Go community before Go Generics were introduced, will not work with a Mojo-typed function . interface{} fn Your struct has to implement at least one of the lifecycle methods like, or , which in this case, would make it conform to or , to be used with functions that accept trait types. __len__ __str__ Sized Stringable This is actually not a real limitation and makes a lot of sense in Mojo, since, with Mojo, if you need dynamic typing, you can just fall back onto the more flexible function, and then apply the usual Python magic to work with unknown types. def The more strict Mojo functions also work on generic structs using types like ; read more about that . fn DynamicVector here Another limitation I see has to do with structs and their methods rather than with traits and is a potential obstacle to implementing Clean Architecture/separating code into different parts. Consider one of the previous examples with a Go vs. Mojo struct method definition: type Duck struct {} func (d Duck) quack() { fmt.Println("Quack!") } @value struct Duck(Quackable): fn quack(self): print("Quack!") The Mojo example, following a more Python-like syntax, nests the method definition directly inside the struct, while the Go version allows it to separate it from the struct itself. In this version, if I have a very long struct with many types and methods, it will be somewhat harder to read. It's not a critical difference though, just something to be aware of. Mojo traits are already pretty useful, despite the language being in its early days. While Go philosophy is all about simplicity and tries to keep the features to a minimum, it's likely that we'll see more functionality added to Mojo traits in the future, making them even more powerful and enabling tons of different use cases.