← Back to AILP Home

Traits

Overview

Traits define shared behavior that types can implement. They serve as constraints for generic type parameters.

trait:Describable = {
    func:describe = int32(int32:self);
};

Implementation

impl:Describable:for:int32 = {
    func:describe = int32(int32:self) {
        pass self + 100;
    };
};

For struct types:

trait:Measurable = {
    func:area = int32(Rect2D:self);
};

impl:Measurable:for:Rect2D = {
    func:area = int32(Rect2D:self) {
        pass self.w * self.h;
    };
};

As Generic Constraints

func<T: Addable>:apply_trait = int32(T:val) {
    pass val;
};

Dynamic Dispatch — dyn

func:process = int32(dyn Describable:item) {
    pass item.describe();
};

Borrow Receivers — $$i and $$m

Trait method parameters can use borrow qualifiers to express ownership semantics:

Qualifier Meaning Aliasing Rule
$$i Immutable borrow (shared) Multiple $$i borrows allowed simultaneously
$$m Mutable borrow (exclusive) Only one $$m borrow at a time, no concurrent $$i

Syntax

Borrow qualifiers appear before the type in trait method parameter lists:

trait:Transformable = {
    func:scale = int32($$i Transformable:self, int32:factor);
    func:mutate = int32($$m Transformable:self, int32:value);
};

Implementation

impl:Transformable:for:Rect2D = {
    func:scale = int32($$i Rect2D:self, int32:factor) {
        // self is immutable — cannot modify fields
        pass self.w * factor;
    };

    func:mutate = int32($$m Rect2D:self, int32:value) {
        // self is mutable — can modify fields
        pass value;
    };
};

When To Use Each

Use $$i when Use $$m when
Method only reads data Method modifies the receiver
Want to allow shared access Need exclusive access
Pure computation / getters Setters / state mutation

Interaction with the Borrow Checker

The compiler enforces borrow rules at compile time:

// OK: multiple immutable borrows
$$i Rect2D:ref1 = ...;
$$i Rect2D:ref2 = ...;  // fine, both are $$i

// ERROR: mutable + immutable at same time
$$m Rect2D:ref1 = ...;
$$i Rect2D:ref2 = ...;  // compile error: cannot borrow while $$m exists

The # Pin Operator

The pin operator # guarantees a variable's memory address stays stable. This prevents the GC from relocating pinned data — essential for zero-copy FFI:

#my_struct;  // pin: address will not move

Pinning is orthogonal to borrowing: a pinned value can still be borrowed with $$i or $$m, but its address is guaranteed stable for the pin's lifetime.

Status

Traits are specified in the language design. Implementation status varies — see the RFC at META/NITPICK/TRAITS_AND_BORROW_SEMANTICS_RFC.md.

Related