Skip to content

Traits

Traits define behaviors that can be implemented by custom types. They are similar to interfaces in other languages but with some key differences. The Rust definition applies well to Ard:

A trait defines the functionality a particular type has and can share with other types. We can use traits to define shared behavior in an abstract way.

Traits consist of method signatures that implementing types must provide:

trait Describable {
fn describe() Str
}

A trait can have multiple methods:

trait Drawable {
fn draw()
fn get_bounds() Rectangle
fn is_visible() Bool
}

Mark a trait method with fn mut when implementations may mutate their receiver:

trait Counter {
fn mut set(value: Int)
fn value() Int
}

Calling set requires mutable receiver access:

fn update(counter: mut Counter) {
counter.set(2)
}
fn inspect(counter: Counter) Int {
counter.value()
}

A mutating implementation cannot satisfy a trait method that omits mut. A non-mutating implementation may satisfy a mutating method because it requires less receiver capability than the contract permits.

Use impl TraitName for TypeName to implement a trait for a specific type:

trait Describable {
fn describe() Str
}
struct Person {
name: Str,
age: Int,
}
impl Describable for Person {
fn describe() Str {
"{self.name} is {self.age} years old"
}
}

Traits can be used as function parameter types to accept any type that implements the trait:

use go:fmt
fn debug(thing: Describable) {
fmt::Println(thing.describe())
}
let person = Person{name: "Alice", age: 30}
debug(person)

Inside debug, only the trait’s methods are available. Accessing thing.name would be a compile-time error because Describable says nothing about a name field.