Traits
Part of: Reef Language Reference Last reviewed on version: 0.9.0 Status: Implemented
Overview
Traits define abstract interfaces that types can implement. They enable:
- Polymorphism through shared interfaces
- Generic constraints (bounds)
- Code organization and API design
Key Features:
- Trait definitions with abstract method signatures
- Implementation blocks for any named type (structs, Active Objects, objects, enums)
- Generic constraints using
where T: TraitName - Multiple trait implementations per type
Trait Definitions
Basic Syntax
Define a trait with trait Name ... end Name:
trait Printable
proc print();
end Printable
The semicolon after method signatures indicates abstract methods (no body).
A method with a body is a type error in 0.9 (Trait default method bodies are not supported); every impl must provide every method.
Default implementations (clone-per-impl) are deferred:
reef-lang#5.
Multiple Methods
Traits can require multiple methods:
trait Comparable
fn compare(other: int): int;
fn equals(other: int): bool;
end Comparable
Method Signatures
Methods can be procedures (no return) or functions (with return):
trait Serializable
fn serialize(): string; // Function - returns value
proc deserialize(s: string); // Procedure - no return
end Serializable
Implementing Traits
Basic Implementation
Use impl TraitName for Type ... end impl:
type Point = struct
x: int
y: int
end Point
impl Printable for Point
proc print()
println("Point")
end print
end impl
Multiple Implementations
A type can implement multiple traits:
type Value = struct
v: int
end Value
impl Printable for Value
proc print()
println("Value")
end print
end impl
impl Comparable for Value
fn compare(other: int): int
self.v - other
end compare
fn equals(other: int): bool
self.v == other
end equals
end impl
Note: an impl block must provide every method the trait declares — the
Comparable trait above requires both compare() and equals().
A type's own methods and its trait-impl methods share one name space: two bodies for the same name (an own method plus an impl method, or two impls providing the same method) are a type error. An own method satisfying a trait so the impl may omit it is not in 0.9.
Objects
impl Trait for Class is legal. Subclasses inherit the base's impls for
method syntax (button.draw() finds impl Trait for Widget). Trait
methods are statically dispatched to that impl target — they are not
virtual. The pattern that does vary per subclass is delegate to a
virtual: the class method is virtual, the impl forwards under a
different name. Same-name own method plus impl is still the error
above. Full example and the where T: Trait subclass case:
040_OBJECTS.md. The
examples/widget_tree.reef showcase runs the pattern on a small tree.
Self Access
Inside trait implementations, use self to access the implementing type's fields:
impl Comparable for Point
fn compare(other: int): int
self.x - other
end compare
fn equals(other: int): bool
self.x == other
end equals
end impl
Generic Constraints
Where Clauses
Constrain generic type parameters with where T: TraitName:
fn describe[T](item: T): int where T: Printable
// T must implement Printable
42
end describe
Using Constrained Functions
Call with types that implement the required trait:
proc main()
let p = Point { x: 1, y: 2 }
let v = Value { v: 100 }
// Both Point and Value implement Printable
let r1 = describe(p) // OK
let r2 = describe(v) // OK
end main
API Reference
Trait Definition
trait TraitName
fn method_name(params...): return_type; // Required function
proc method_name(params...); // Required procedure
end TraitName
Trait Implementation
impl TraitName for Type
fn method_name(params...): return_type
// Implementation body
end method_name
proc method_name(params...)
// Implementation body
end method_name
end impl
Generic Constraint
fn function_name[T](param: T): return_type where T: TraitName
// Body - T guaranteed to implement TraitName
end function_name
Current Limitations
Note: Trait constraint checking is implemented, but dynamic dispatch (calling trait methods through a trait reference) is not yet supported. Current usage:
- Define traits and implementations for type documentation
- Use where clauses to constrain generic functions
- Call methods directly on concrete types
Multiple constraint bounds work: where T: A + B requires both, and the
bound is checked against every listed trait. This holds on generic functions
and, since 0.9.2, on generic structs.
Future versions will add:
- Dynamic dispatch through trait objects
- Default method implementations (clone-per-impl: reef-lang#5; 0.9 rejects a body on a trait method rather than silently ignoring it)
Type-Level Bounds on Generic Structs
Since 0.9.2 a generic struct can carry its own bounds, which its method bodies then rely on:
import core.hash
type Box[K] where K: Hash = struct
key: K
fn code(): int
return self.key.hash()
end code
end Box
Without the bound, self.key.hash() is a type error: K is just a type
variable, and nothing says it has a hash method. The bound is what makes
the call legal, and it is enforced at every instantiation — Box[float]
is rejected, because float has no impl Hash.
where is available on generic structs only. Active objects and passive
objects do not take type-level bounds.
Constructors are ordinary generic functions and repeat the bound:
fn create[K](): Box[K] where K: Hash
return new Box[K]()
end create
Where Implementations Are Visible
An impl is visible program-wide once the module declaring it is in the
build graph. It does not need to be imported directly, and neither does the
trait.
That matters for bounded containers. collections.hashmap declares
HashMap[K, V] where K: Hash and gets impl Hash for string from
core.hash. A program can therefore write:
import collections.hashmap
proc main()
let ages = hashmap.create[:string, int]()
ages.set("ada", 36)
end main
core.hash is never named here. Before 0.9.2 the bound was checked only
against impls the calling module could see, so this failed with
"Type 'string' does not implement trait(s): Hash" even though the impl
existed in the program.
Traits themselves are importable with export type Name, and bind under
their bare name in the importer — impl and where take an identifier, not
a qualified path:
import core.hash
type User = struct
id: int
end User
impl Hash for User
fn hash(): int
return self.id.hash()
end hash
end impl
Note the impl target may be a primitive: int, string and bool can
carry implementations. float cannot — NaN and -0.0 break the contract
that equal values hash equally.
Examples
Complete Example
// Define traits
trait Printable
proc print();
end Printable
trait Comparable
fn equals(other: int): bool;
end Comparable
// Define type
type Point = struct
x: int
y: int
end Point
// Implement traits
impl Printable for Point
proc print()
println("Point")
end print
end impl
// Generic function with constraint
fn describe[T](item: T): int where T: Printable
42
end describe
proc main()
let p = Point { x: 1, y: 2 }
let result = describe(p)
println("Done!")
end main