Traits
A trait declares a set of signatures that any type can satisfy. The definition contains no data — only the interface. Several independent traits describe orthogonal behaviors:
Displayable and Shape are two traits; Square implements both with separate bodies.
// Feature: Trait definition — shared interface
// Syntax: `trait Name { fn method(self) -> Ret /* no body */ }`
// When to use: describe a set of methods that multiple types will
// implement (ad-hoc polymorphism).
// Trait without defaults — only signatures. Each implementer is
// required to provide a body for `display`.
trait Displayable {
fn display(self) -> str
}
// Trait with multiple signatures.
trait Shape {
fn area(self) -> float
fn name(self) -> str
}
struct Square {
side: float,
}
impl Displayable for Square {
fn display(self) -> str {
return "Square({self.side})"
}
}
impl Shape for Square {
fn area(self) -> float {
return self.side * self.side
}
fn name(self) -> str {
return "square"
}
}
let s = Square { side: 4.0 }
print(s.display()) // Square(4)
print(s.area()) // 16
print(s.name()) // square
// expected:
// Square(4)
// 16
// square
impl Trait for Type provides the body of each method required by the trait.
Distinct types have independent implementations — each one resolves the same
interface in its own way:
Circle and Rectangle implement HasArea independently.
// Feature: Trait implementation — `impl Trait for Type`
// Syntax: `impl Trait for Type { fn method(self) { ... } }`
// When to use: declare that a concrete type satisfies a trait by
// providing the body for each required method.
trait HasArea {
fn area(self) -> float
}
struct Circle {
radius: float,
}
struct Rectangle {
width: float,
height: float,
}
// Each `impl Trait for Type` is independent.
impl HasArea for Circle {
fn area(self) -> float {
return 3.14159 * self.radius * self.radius
}
}
impl HasArea for Rectangle {
fn area(self) -> float {
return self.width * self.height
}
}
let c = Circle { radius: 5.0 }
let r = Rectangle { width: 3.0, height: 4.0 }
// The `area` method resolves correctly for each type.
print(c.area()) // 78.53975
print(r.area()) // 12
// expected:
// 78.53975
// 12
A trait can include default methods — with a body already defined in the trait declaration. The implementor can accept the default or override it:
Person uses the default behavior of hello; Pet replaces it with a custom greeting.
// Feature: Default implementation in traits
// Syntax: inside the `trait`, the fn already has a body `{ ... }`
// When to use: provide default behavior derived from other trait
// methods. Implementers can accept the default or override it in
// `impl Trait for Type`.
//
// Note: the checker resolves methods by looking at the type's
// `impl`. To use the default, we still copy the delegated call —
// that is, `impl` must list ALL trait methods. Here we show
// override and explicit delegation.
trait Greet {
fn name(self) -> str
// Default — implementers MAY redefine.
fn hello(self) -> str {
return "Hello, {self.name()}!"
}
}
struct Person {
full: str,
}
// Accepts the default — copies the body in the impl.
impl Greet for Person {
fn name(self) -> str {
return self.full
}
fn hello(self) -> str {
return "Hello, {self.name()}!"
}
}
struct Pet {
nickname: str,
}
// Overrides the default with something specific.
impl Greet for Pet {
fn name(self) -> str {
return self.nickname
}
fn hello(self) -> str {
return "Hi {self.nickname}, who's a good one?"
}
}
let alice = Person { full: "Alice" }
let rex = Pet { nickname: "Rex" }
print(alice.hello()) // Hello, Alice!
print(rex.hello()) // Hi Rex, who's a good one?
// expected:
// Hello, Alice!
// Hi Rex, who's a good one?
A single type can satisfy multiple traits at the same time, each in its own
impl Trait for Type block. The methods from all traits are available on the
instance:
Circle implements HasArea, Named, and Describable — describe combines the other two.
// Feature: Multiple traits on the same type
// Syntax: several independent `impl Trait for Type`
// When to use: combine orthogonal behaviors — the type satisfies
// each trait separately.
trait HasArea {
fn area(self) -> float
}
trait Named {
fn name(self) -> str
}
trait Describable {
fn describe(self) -> str
}
struct Circle {
radius: float,
}
impl HasArea for Circle {
fn area(self) -> float {
return 3.14159 * self.radius * self.radius
}
}
impl Named for Circle {
fn name(self) -> str {
return "circle"
}
}
impl Describable for Circle {
fn describe(self) -> str {
return "{self.name()} of area {self.area()}"
}
}
let c = Circle { radius: 2.0 }
print(c.area()) // 12.56636
print(c.name()) // circle
print(c.describe()) // circle of area 12.56636
Challenge
Create a trait Resizable with a method scale(self, factor: float) -> Self.
Implement it for Circle and Rectangle from the examples above.