Traits & Impls
Traits declare requirements (fields and method signatures); impl blocks
attach methods to a concrete type or declare conformance.
Trait Definitions
// Fields only
pub trait Named {
name: String
}
// Fields and methods
pub trait Shape {
color: String
fn area(self) -> I32
fn perimeter(self) -> I32
}
// Methods only
pub trait Drawable {
fn draw(self) -> Boolean
fn visible(self) -> Boolean
}
// Trait composition (inheritance)
pub trait Entity: Named + Identifiable {
createdAt: I32
}
// Generic trait
pub trait Collection<T> {
items: [T]
}
Trait Rules:
- Fields listed without
fnare structural requirements (the struct must have them) fnsignatures listed without a body are method requirements- Trait composition (
+) combines requirements from multiple traits - A type satisfies a trait by providing all required fields and all required methods
Impl Blocks
Impl blocks add methods to a struct (inherent impl) or declare trait conformance (impl Trait for Struct).
Inherent impl: methods belong to the struct:
pub struct Counter {
value: I32
}
impl Counter {
fn increment(self) -> I32 {
self.value + 1
}
fn reset(self) -> Counter {
Counter(value: 0)
}
}
Impl Trait for Type
Declare that a type conforms to a trait using impl Trait for Type:
pub trait Named {
name: String
}
pub trait Drawable {
fn draw(self) -> Boolean
}
pub struct Circle {
name: String,
radius: I32
}
// Declare conformance (fields are checked against struct definition)
impl Named for Circle {}
// Provide required methods
impl Drawable for Circle {
fn draw(self) -> Boolean {
self.radius > 0
}
}
Trait composition requires a separate impl block for each trait in the hierarchy:
pub trait Base {
fn id(self) -> I32
}
pub trait Extended: Base {
fn name(self) -> String
}
pub struct Item {
value: I32
}
impl Base for Item {
fn id(self) -> I32 {
self.value
}
}
impl Extended for Item {
fn name(self) -> String {
"item"
}
}
Conformance rules:
impl Trait for Typeis the only way to declare trait conformance- Struct fields required by the trait must be present in the struct definition
- All
fnsignatures in the trait must be implemented in the impl block - Method signatures (parameter count and return type) must match exactly
- Separate impl blocks for inherited traits; only provide methods declared in that trait
Trait-Bounded Polymorphism
A trait can stand in as a value type at parameter, return, let annotation, struct field, and closure positions. Method calls on a trait-typed binding are lowered through the trait's per-trait vtable (virtual dispatch). When two if-branches construct different concrete types implementing the same trait, the if-expression unifies to that trait without an explicit cast.
pub trait Printable {
fn label(self) -> String
}
pub struct Doc {
text: String
}
impl Printable for Doc {
fn label(self) -> String { self.text }
}
// Static dispatch via a generic-bounded parameter — no vtable, the
// concrete type is known after monomorphisation.
fn print_it<T: Printable>(item: T) -> String {
item.label()
}
// Virtual dispatch via a trait-typed binding — the receiver carries a
// vtable index alongside its data; the call resolves at runtime.
fn print_any(item: Printable) -> String {
item.label()
}
Pick whichever fits: bounded generics produce a separate specialised function per concrete type and avoid an indirect call; trait-typed bindings keep one function and dispatch through the vtable. Both forms are checked at compile time against the trait's method signatures.
Generic Traits
Traits can themselves be generic, and constraints / impls can carry the concrete arguments:
pub trait Container<T> {
fn get(self) -> T
}
pub struct Box {
value: I32
}
impl Container<I32> for Box {
fn get(self) -> I32 { self.value }
}
fn unwrap<T: Container<I32>>(b: T) -> I32 {
b.get()
}
The monomorphisation pass clones generic traits, structs, enums,
and functions per unique argument tuple, then rewrites every
reference (including DispatchKind::Virtual on now-concrete
receivers) to point at the specialised clone. After mono runs, no
generic definitions remain in the IR.
Allowed trait positions:
- Generic constraint:
<T: Trait>or<T: Trait<X>> - Impl target:
impl Trait for Fooorimpl Trait<X> for Foo - Trait composition:
trait A: B + C
Rejected trait positions (use a generic bound instead):
- Function parameter type:
fn foo(x: Trait)✗ - Function return type:
fn make() -> Trait✗ - Let annotation:
let x: Trait = ...✗ - Struct/enum field:
field: Trait✗ - Closure params/return:
(x: Trait) -> I32✗