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:

  1. Define traits and implementations for type documentation
  2. Use where clauses to constrain generic functions
  3. 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