Traits
Traits define shared behavior across types.
Defining a trait
trait Eq {
fn eq(self: &Self, other: &Self): bool
}Implementing a trait
struct Point { x: i32, y: i32 }
impl Eq for Point {
fn eq(self: &Self, other: &Self): bool {
return self.x == other.x && self.y == other.y
}
}Default methods
trait Greet {
fn greet(self: &Self): i32 {
return 42 // default implementation
}
}
struct Cat { name: i32 }
impl Greet for Cat {} // uses the defaultGeneric bounds
Constrain type parameters to require trait implementations:
fn printIfEqual<T: Eq>(a: &T, b: &T) {
if a.eq(b) {
print("equal!")
}
}Multiple bounds:
fn process<T: Eq + Hash>(item: &T) { ... }Supertraits
trait Ord: Eq {
fn compare(self: &Self, other: &Self): i32
}Implementing Ord requires Eq to be implemented as well.
@derive
Auto-generate trait implementations:
@derive(Eq)
struct Point { x: i32, y: i32 }
print(Point { x: 1, y: 2 } == Point { x: 1, y: 2 }) // trueThree traits are built-in derivable: Eq, Clone, and Json. Eq and Clone are also derived automatically for any plain struct whose fields support them, so == and .clone() exist with no annotation at all. The exceptions are resource types: a struct with a Drop impl or @noCopy never gets an automatic clone(), because duplicating it would release the resource twice. A user-written trait becomes derivable with a derive Trait { … } template.
Operator overloading
Implementing Add, Sub, Mul, Div or Eq for your type makes the corresponding operator work on it. Dispatch is static — there is no runtime lookup.
struct Vec2 {
x: i32,
y: i32,
}
impl Add for Vec2 {
fn add(self: &Self, other: &Self): Self {
return Vec2 { x: self.x + other.x, y: self.y + other.y }
}
}
let sum = Vec2 { x: 3, y: 4 } + Vec2 { x: 5, y: 6 }
print(sum.x) // 8Interfaces
A trait call is resolved at compile time, once per concrete type. When the concrete type is only known at runtime (a list of mixed shapes, a plugin), use an interface. An interface lists methods. Any type that has those methods satisfies it, with no declaration naming the interface: inherent methods and trait impls both count. An interface value is a fat pointer to the data plus a table of that type's methods, and each call goes through the table.
&Greeter as a parameter accepts any type that satisfies Greeter. Heap<Greeter> owns one, so a Vec<Heap<Greeter>> can hold mixed types. This is Milo's trait object.
interface Greeter {
fn greet(self: &Self): string
}
struct Dog { name: string }
impl Dog {
fn greet(self: &Self): string { return "woof from " + self.name }
}
struct Cat {}
impl Cat {
fn greet(self: &Self): string { return "meow" }
}
fn sayHello(g: &Greeter) {
print(g.greet())
}
fn main(): i32 {
sayHello(Dog { name: "Rex" }) // woof from Rex
var all: Vec<Heap<Greeter>> = Vec.new()
all.push(Heap(Dog { name: "Fido" }))
all.push(Heap(Cat {}))
for g in all {
print(g.greet()) // woof from Fido, meow
}
return 0
}What's not here yet
dyn Trait: atraitdispatches statically only, and&Eqis not a type. Use an interface for runtime dispatch.- Associated types
whereclauses
None of these is planned; associated types are listed under Not Planned.