11. Traits and derives¶
Traits describe shared behavior. Derives automatically generate common behavior for your types.
Derives: add behavior without boilerplate¶
Derives add behavior to a type. One of the most common is Debug, which lets you print a structured representation of a
value.
Coming from Python?
Derives are like getting common “dunder methods” (__repr__, __eq__, etc.) without writing them by hand.
@derive(Debug)
model Point:
x: int
y: int
def main() -> None:
p = Point(x=1, y=2)
println(f"{p:?}")
Debug formatting (:?)¶
The :? inside an f-string means “debug formatting”.
Coming from Python?
Debug formatting (:?) is like Python’s __repr__: it shows the type name and fields in a structured format.
Traits: shared contracts¶
Traits let you define a shared contract. In a trait, a method can either:
- Use
...to mean “implementers must provide this” (required methds) - Provide a default implementation (Rust-like default methods)
Coming from Python?
Traits are like a typed interface (represented in Python by a Protocol or abc.ABC): “anything that implements
these methods can be treated as this capability”.
Default methods and adopter fields (@requires)¶
If a trait default method accesses adopter fields directly (for example self.name), the trait must declare those fields
in @requires(...). Mutating fields still requires mut self (same as normal methods).
Example:
trait Greetable:
# Required method: implementers must provide this
def name(self) -> str: ...
# Default method: implementers can provide this
def greet(self) -> str:
return f"Hello, {self.name()}!"
model User with Greetable:
username: str
def name(self) -> str:
return self.username
def main() -> None:
u = User(username="alice")
println(u.greet()) # outputs: Hello, alice!
Mutation uses mut self (and the field must be declared via @requires(...)):
@requires(count: int)
trait Counter:
def bump(mut self) -> None:
self.count += 1
class Thing with Counter:
count: int = 0
def main() -> None:
t = Thing()
t.bump()
Try it¶
- Add a second derived type and print it with
:?. - Create a small trait (for example
Describable) and implement it for a model. - Call the trait method.
One possible solution
@derive(Debug)
model Point:
x: int
y: int
trait Describable:
def describe(self) -> str: ...
model User with Describable:
username: str
def describe(self) -> str:
return f"user={self.username}"
def main() -> None:
println(f"{Point(x=1, y=2):?}")
println(User(username="alice").describe())
Where to learn more¶
- Derives and traits (full reference): Derives & Traits
Next¶
Back: 10. Models vs classes
Next chapter: 12. Newtypes (stronger types).