Skip to content
CryoCryo home
LanguageAbstraction

10Traits

A trait names a set of methods that a type may implement. Traits are how generic code expresses requirements on its type parameters and how the standard library models capabilities such as equality, ordering, hashing, formatting, and I/O.

10.1 Declaring a Trait

type trait Eq {
    equals(&this, other: &This) -> boolean;
}

Inside the trait body, This refers to the implementing type. Methods may have default bodies:

type trait Read {
    read(mut &this, buf: u8*, len: u64) -> Result<u64, IoError>;

    /// Default: keep reading until end-of-stream or error.
    read_all(mut &this, out: mut &Array<u8>) -> Result<u64, IoError> {
        // ... default implementation calls self.read in a loop ...
    }
}

A trait may inherit from a base trait; implementations of the derived trait must also implement the base:

type trait Ord : Eq {
    compare(&this, other: &This) -> Ordering;
}

An implementation of the derived trait for a type that does not implement the base is refused where the implement block is written (E0312: "Square does not implement Eq, which Ord requires, so Square cannot implement Ord"). A base written with arguments, type trait Sized<U> : Measure<U>, must be implemented at them, with the derived implementation's arguments put in: Sized<i64> for a type needs Measure<i64> for it. A generic implementation, implement<T> trait Ord for Box<T>, needs an implementation of the base for the same generic type, whose where-clause is not compared with the derived one's.

A trait is not a type. Written where a value's type goes - p: Shape, a return type, a field, a local, a type argument, an alias's target - it is refused where it is written (E0313), and so are &Shape and Shape* with Shape a trait. Code that accepts any type implementing a trait takes a bounded type parameter instead (section 10.3), or a trait object (section 10.7):

function area_of(p: Shape) -> i32 { ... }                      // error[E0313]: `Shape` is a trait, not a type
function area_of(p: &Shape) -> i32 { ... }                     // error[E0313]: `Shape` is a trait, so `&Shape` is not a type
function area_of<T>(p: &T) -> i32 where T: Shape { ... }       // ok: one copy per implementing type
function area_of(p: virtual &Shape) -> i32 { ... }             // a trait object: one copy for every type

&This inside a trait names the implementing type and is not a trait object.

10.2 Implementing a Trait

implement trait <Trait> for <Type> { ... } provides the method bodies for a concrete type.

implement trait Eq for i32 {
    equals(&this, other: &i32) -> boolean {
        return this == *other;
    }
}

implement trait Ord for i32 {
    compare(&this, other: &i32) -> Ordering {
        if (this < *other) { return Ordering::Less;    }
        if (this > *other) { return Ordering::Greater; }
        return Ordering::Equal;
    }
}

You may implement a trait for any type a program can name, including primitive types.

An implementation's methods are the trait's methods, each with the signature the trait declares, read with This as the type implemented for (written as This, or as the type itself over the head's own parameters, Wrap<T> in implement<T> trait Tr for Wrap<T>), the trait's parameters as the head's arguments, its associated types as the implementation binds them, and a method's own type parameters by position, whatever they are named. Every disagreement is refused at the method, or at the head for a missing one (E0312):

  • a method the trait declares without a body is missing; one the trait gives a body may be left out, and the trait's body is used;
  • a method the trait does not declare;
  • a static method written with a receiver, or the reverse;
  • a receiver of another form (this, &this, mut &this);
  • a different number of the method's own type parameters, or of its parameters;
  • a parameter of another type, or another return type.
type trait Shape {
    area(&this) -> i32;
    scale(mut &this, k: i32) -> void;
}

implement trait Shape for struct Sq {
    area(&this) -> i64 { ... }            // error[E0312]: `area` returns `i32` in trait `Shape`, but `i64` here
    scale(&this, k: i32) -> void { ... }  // error[E0312]: `scale` takes `mut &this` in trait `Shape`, but `&this` here
}

A trait has at most one implementation for any type, and that is decided by the heads alone. Two implementations of one trait whose heads unify - some type both would accept - are refused at the later one (E0308), whether the heads are identical, one generic and one concrete (Show for Wrap<T> beside Show for Wrap<i32>: a Wrap<i32> would select both), or told apart only by their where clauses (Emit for Holder<T> where T: Alpha beside Emit for Holder<T> where T: Beta: a bound narrows which types a head is for but nothing can prove two bounds disjoint). There is no specialisation and no selection by declaration order. Heads that no type satisfies together are distinct implementations: Show for Pair<T, T> beside Show for Pair<i32, u8>, or From<i8> for u64 beside From<u8> for u64.

10.3 Trait Bounds with where

A generic parameter is constrained to types that implement specific traits via a where clause:

function smallest<T>(xs: &Array<T>) -> Option<T>
    where T: Ord + Copy {
    if (xs.length() == 0) { return Option::None; }
    mut best: T = xs.get(0).unwrap();
    for (mut i: u64 = 1; i < (xs.length() as u64); i++) {
        const next: T = xs.get(i).unwrap();
        if (next.compare(&best) == Ordering::Less) {
            best = next;
        }
    }
    return Option::Some(best);
}

Multiple bounds on the same parameter are joined with +. Multiple constrained parameters are separated with ,:

where T: Hash + Eq, V: Clone

A where clause is the only way to constrain a parameter; there is no inline <T: Bound> form. It is written on functions and methods, on implement blocks, and on type declarations — a struct, union or enum — after the parameter list and before the body:

type struct Holder<T> where T: Tagged {
    inner: T;
    label(&this) -> i32 { return this.inner.tag(); }   // reached through the bound
}

type enum Slot<T> where T: Tagged {
    Empty;
    Full(T);
}

Every trait a bound or a supertrait names must be one in scope where it is written; a name that finds none is refused there (E0242, "cannot find trait X in this scope"), whether or not anything calls through the bound.

type trait Sub : Missing { go(&this) -> i32; }                // error[E0242]: cannot find trait `Missing` in this scope
function f<T>(x: &T) -> i32 where T: Nowhere { return 1; }    // error[E0242]: cannot find trait `Nowhere` in this scope

A type's bound is checked once per instantiation: Holder<Plain> with a Plain that does not implement Tagged is an error (E0306) at the annotation that wrote it, with the bound as declared beside it. An instantiation reached without an annotation — a literal, a static call — is refused the same way where it is specialised.

Inside the body that declares it, a parameter has only the methods its bounds give it. A method call on a parameter that no bound in scope names — the function's where clause, the enclosing implement block's, or the type declaration's — is an error (E0358) where the call is written, whether or not anything instantiates the body.

type struct Sink<W> {
    w: W;
    total(&this) -> u64 { return this.w.count(); }   // error[E0358]: no method named `count` found on type `W`
}

The same holds for operators and fields. An operator applies to a parameter only when a bound licenses it - the language's own trait for that operator (Add for +, Ord for <, Eq for ==; see section 10.6) - and a bound never gives a parameter fields, whatever the type it is later instantiated with declares. Both are refused on the generic body, whether or not anything instantiates it:

function add<T>(a: T, b: T) -> T { return a + b; }                      // error[E0229]: the `+` operator cannot be applied to `T`: its bounds do not give it `std::core::ops::Add`
function less<T>(a: T, b: T) -> boolean where T: Ord { return a < b; }  // fine: `Ord` licenses `<`

type struct Cell<T, A> { v: T; tag: A; }
implement struct Cell<T, A> {
    tagged(&this) -> i32 { return this.tag.n; }    // error[E0204]: no field `n` on type `A`
    tag_ref(&this) -> &A { return &this.tag; }     // fine; read `.n` where `A` is a concrete type
}

A user trait that is merely named Add does not license +; the bound must name the language's trait (std::core::ops::Add, in the prelude).

Inside the body, a parameter is one type: a binding, a return, an assignment, a branch or a call's argument that expects T takes a value of type T and nothing else, whatever T is later instantiated with. Array::get answers Option<T>, so it is matched or unwrapped before it is used as a T. A method whose own where clause constrains the owner's parameter (get requires T: Copy) is callable on the body's Array<T> only when the body's bounds give T that trait. Inside a static match (T) arm, T is the arm's type, and that includes the declared return type and a field declared T:

function first<T>(xs: &Array<T>) -> T where T: Copy {
    const v: T = xs.get(0);           // error[E0200]: expected `T`, found `Option<T>`
    return v;
}

function push_first<T>(xs: mut &Array<T>) -> void where T: Copy {
    xs.push(xs.get(0));               // error[E0214]: expected `T`, found `Option<T>`
}

function first_of<T>(xs: &Array<T>) -> Option<T> where T: Clone {
    return xs.get(0);                 // error[E0358]: the trait bound `T: Copy` is not satisfied
}

function halve<T>(x: T) -> T {
    static match (T) {
        f64 => { const h: f64 = x / 2.0; return h; }   // fine: in this arm `T` is `f64`
        _   => { return x; }
    }
}

10.4 Standard Library Traits

TraitPurpose
CopyMarker for types that are bitwise-copyable (no Drop impl).
DropExplicit destructor; types implementing it are non-Copy.
CloneExplicit deep duplication via clone().
DefaultA canonical zero value: static default() -> This.
EqEquality (backs == / !=; see section 10.6).
Ord (: Eq)Total ordering via compare(...) -> Ordering (backs < > <= >=).
HashType-level hashing into a Hasher.
Add/Sub/Mul/Div/Rem, Neg, BitAnd/BitOr/BitXor/Shl/Shr, Not/BitNot, Index, DerefOperator overloading - see section 10.6.
IteratorLazy sequence with an associated Item and next() -> Option<Item> (see section 10.5).
IntoIteratorConversion into an iterator.
From<T> / Into<T>Infallible conversions.
TryFrom<T> / TryInto<T>Fallible conversions returning Result.
Read / WriteByte-level I/O.
Display / DebugFormatting.
FmtWriteSink trait for the formatter.
AllocatorHeap allocation strategy.

Every standard-library trait is declared in stdlib/core/ with the exception of Read/Write (in stdlib/io/traits.cryo), Display/Debug/FmtWrite (in stdlib/fmt/), and Allocator (in stdlib/alloc/allocator.cryo).

The collection iterator entry points (Array::iter, HashMap::keys/values, Str::split, ...) return implement Iterator<...> rather than naming their concrete cursor structs, so you consume them through the trait and a for-in loop without ever spelling the underlying type.

10.5 Associated Types

A trait may declare an associated type - a type that each implementation supplies, named once in the trait and referred to by every method. The standard Iterator is written this way:

type trait Iterator {
    type Item;                              // each impl chooses its element type
    next(mut &this) -> Option<This::Item>;  // `This::Item` projects it
    // ... defaults (count, fold, map, filter, ...) all in terms of This::Item ...
}

This::Item is a projection: inside the trait it stands for "the Item this implementation bound". Projections also work off a generic parameter - a generic adapter names its source's element as I::Item:

type struct MapIter<I, O> where I: Iterator { inner: I; f: (I::Item) -> O; }

A projection is answered by the parameter's bounds, so I::Item needs a bound on I whose trait declares Item. Without one, or when no bound's trait declares the name, the projection is refused where it is written (E0203, "associated type Item not found for I"). A bound on a trait that inherits Iterator answers too: the projection resolves through the bound trait's supertraits.

type trait DoubleEnded : Iterator { next_back(mut &this) -> Option<This::Item>; }

function first<I>(it: mut &I) -> Option<I::Item> where I: DoubleEnded {   // Iterator's `Item`
    return it.next();
}

type struct Loose<I> { f: () -> I::Item; }   // error[E0203]: associated type `Item` not found for `I`

Binding the associated type. An implementation binds each associated type in one of two ways:

// 1. Positional sugar - `Iterator<i32>` == `Iterator<Item = i32>`. Available
//    only when the trait has no generic params of its own (Iterator's case).
implement trait Iterator<i32> for struct Counter { ... }

// 2. Explicit body form - always available, and required when a value
//    expression rather than sugar is clearer.
implement trait Iterator for struct Counter {
    type Item = i32;
    ...
}

The positional form scales to where-clause adapters, whose element flows from the source: implement<I, A> trait Iterator<A> for struct TakeIter<I> where I: Iterator<A> binds Item := A := I::Item.

Declaration-site bounds. An associated type may carry a bound (type Item: Copy;). Every impl's concrete binding is checked against it - an impl whose Item does not satisfy the bound is rejected with E0306:

type trait Seq { type Item: Copy; next_one(&this) -> i32; }
implement trait Seq<NotCopy> for struct Holder { ... }   // E0306: Item not Copy

Diagnostics. Three errors guard the rules:

  • E0306 - an impl binds an Item that does not satisfy a declaration-site bound (type Item: Copy;).
  • E0309 - an impl of a trait that declares an associated type binds none of them (no positional arg and no type Item = ...; body). The projection could never reduce, so it is rejected up front.
  • E0310 - an associated type bound positionally on a trait that also has generic parameters. Positional args fill the declared generic params in order, so an associated type of such a trait must be bound with the explicit body form (type Out = ...;).

Because Cryo monomorphises, a projection is fully resolved at compile time: MapIter<Range<i32>, i64>::Item reduces to i64 with no runtime cost.

10.6 Operator Overloading

Operators on a user-defined type desugar, in the compiler, to a call to the corresponding operator trait method. Implement the trait and the operator works on your type; the rewrite happens during semantic analysis, so there is no runtime dispatch and the result monomorphises like any other method call.

The rewrite is type-directed and LHS-driven: a OP b dispatches on the type of a, and it only fires when the built-in rule does not already apply. Primitive arithmetic (1 + 2), pointer stepping, and native integer/pointer comparison keep emitting raw instructions - primitives deliberately do not implement the arithmetic traits. The operator traits live in stdlib/core/ops.cryo (Eq/Ord are in stdlib/core/cmp.cryo).

Operator(s)Trait (core::ops / core::cmp)Method / desugar
+ - * / %Add Sub Mul Div Rema + b -> a.add(&b) (etc.)
- (unary)Neg-a -> a.neg()
& | ^ << >>BitAnd BitOr BitXor Shl Shra & b -> a.bitand(&b) (etc.)
! ~ (unary)Not BitNot!a -> a.not(), ~a -> a.bitnot()
== !=Eqa == b -> a.equals(&b), a != b -> !a.equals(&b)
< > <= >=Ord (: Eq)a < b -> a.compare(&b).is_lt() (is_gt/is_le/is_ge)
a[i]Index<Idx, Output>a[i] -> *(a.index(i))
*a (deref)Deref<Target>*a -> *(a.deref())

Each arithmetic/bitwise trait carries Rhs and Output type parameters, so an operator can mix types (add a scalar to a vector, shift by a plain integer) and choose its result type. The right operand is taken by reference (rhs: &Rhs) to avoid moving an owned aggregate.

type struct Vec2 { x: i64; y: i64; }

implement trait Add<Vec2, Vec2> for struct Vec2 {
    add(&this, rhs: &Vec2) -> Vec2 { return Vec2 { x: this.x + rhs.x, y: this.y + rhs.y }; }
}
implement trait Mul<i64, Vec2> for struct Vec2 {          // scalar on the right
    mul(&this, rhs: &i64) -> Vec2 { return Vec2 { x: this.x * *rhs, y: this.y * *rhs }; }
}
implement trait Neg<Vec2> for struct Vec2 {
    neg(&this) -> Vec2 { return Vec2 { x: -this.x, y: -this.y }; }
}

const a: Vec2 = Vec2 { x: 1, y: 2 };
const b: Vec2 = Vec2 { x: 3, y: 4 };
const c: Vec2 = a + b;      // Vec2 { 4, 6 }
const d: Vec2 = a * 3;      // Vec2 { 3, 6 }
const e: Vec2 = -a;         // Vec2 { -1, -2 }

Equality and ordering. Implement Eq for ==/!= and Ord (which extends Eq) for < > <= >=. A single compare backs all four relational operators through the Ordering predicates:

implement trait Eq for struct Vec2 {
    equals(&this, other: &Vec2) -> boolean { return this.x == other.x && this.y == other.y; }
}
implement trait Ord for struct Vec2 {
    compare(&this, other: &Vec2) -> Ordering {
        if (this.x != other.x) { return this.x.compare(&other.x); }
        return this.y.compare(&other.y);
    }
}
const eq: boolean = a == b;   // a.equals(&b)      -> false
const lt: boolean = a < b;    // a.compare(&b).is_lt() -> true

Compound assignment routes through the same trait: a += b evaluates a.add(&b) and stores the result back into a. This holds for every binary arithmetic, bitwise, and shift operator (+= -= *= /= %= &= |= ^= <<= >>=).

Indexing. Index<Idx, Output>::index returns a pointer (Output*), so the desugar a[i] -> *(a.index(i)) is a place: one implementation serves reads (v = a[i]), writes (a[i] = v), and compound assignment (a[i] += v). Fixed-array, string and pointer indexing keep their native path. A type may implement Index once per index type; the call is answered by the implementation whose Idx is the index's own type. Array<T> and [T] are indexed this way: the standard library implements Index for Array<T> once per integer type, marked ![inline], so xs[i] takes an index of any integer type, costs no call, and an index at or past the length (a negative one included) panics. In a generic body, a container whose type carries the body's parameters (xs: &Array<T>) is indexed through its own implementation exactly as a concrete one is.

type struct Grid { cells: [i32; 9]; }
implement trait Index<u64, i32> for struct Grid {
    index(&this, i: u64) -> i32* { return &this.cells[i]; }
}
mut g: Grid = ...;
g[0] = 42;        // *(g.index(0)) = 42
g[0] += 1;        // index called once

Dereference and auto-deref. Deref<Target>::deref also returns a pointer (Target*), so *b -> *(b.deref()) is likewise a place (read/write/compound). Member access coerces through Deref too: when a receiver lacks a field or method but implements Deref, b.field / b.method() retry on the pointee (transitively), inserting the .deref() chain for you - the smart-pointer pattern.

type struct Boxed<T> { value: T; }
implement<T> trait Deref<T> for struct Boxed<T> {
    deref(&this) -> T* { return &this.value; }
}
mut b: Boxed<Vec2> = Boxed::<Vec2> { value: Vec2 { x: 1, y: 2 } };
const v: Vec2 = *b;   // *(b.deref())
b.x = 10;             // auto-deref: (*b.deref()).x - b has no field `x`

Reference operands. A by-reference operand (&T, the common container/parameter case) overloads exactly like a value - v[i], a + b, -a, and a == b all work when a/v is a &T. In particular &x == &y (where the referent implements Eq) compares by value (x.equals(&y)), not by pointer identity. A raw pointer T* is never unwrapped this way: p == null and pointer arithmetic stay on the primitive path.

Reflected (primitive left operand). When the left operand is a primitive and the right is a user type, the operator dispatches to a trait implemented on the primitive - 2 * v calls (2).mul(&v) given implement trait Mul<Vec2, Vec2> for i64. This is the one case where the right operand's type pulls in the impl; Eq/Ord are excluded (their operands share This).

In generic code an operator on a type parameter is licensed by its bounds alone, checked once on the generic body rather than per instantiation: under where T: Add<T, T>, a + b is Add::add, and with no such bound it is refused (E0229) even if every type the body is instantiated with implements Add (see section 10.3). The desugar is carried through monomorphisation, so Container<i32>::index (etc.) specialises correctly. On a concrete type that lacks the required impl, the compiler names the missing trait.

function sum<T>(a: T, b: T) -> T where T: Add<T, T> { return a + b; }
const v: Vec2 = sum::<Vec2>(a, b);   // Vec2 { 4, 6 }

10.7 Trait Objects

virtual &Shape, virtual mut &Shape and virtual Shape* are trait objects: a value of any type implementing Shape, held with a table of that type's methods for the trait. Code taking one is compiled once and serves every implementing type, where a bounded type parameter (section 10.3) is compiled once per type.

type trait Shape { area(&this) -> i32; }
type struct Square { side: i32; }
type struct Rect { w: i32; h: i32; }
implement trait Shape for struct Square { area(&this) -> i32 { return this.side * this.side; } }
implement trait Shape for struct Rect { area(&this) -> i32 { return this.w * this.h; } }

function total(shapes: [virtual &Shape]) -> i32 {
    mut sum: i32 = 0;
    for (s in shapes) { sum = sum + s.area(); }   // each call goes through the value's table
    return sum;
}

const sq: Square = Square { side: 2 };
const r: Rect = Rect { w: 2, h: 3 };
const n: i32 = total([&sq, &r]);                 // 10

Spelling. The three forms borrow the value, borrow it mutably, or point at it. A trait object is a type of its own, not a reference or a pointer: it is two words, the value's address and its table's, 16 bytes at 8-byte alignment in every form, and it is copied as a reference is, owning nothing. The suffixes after an object apply to it: virtual Shape*? is an optional object pointer, [virtual &Shape] an array of objects, and virtual Shape** a pointer to an object pointer.

  • virtual written on anything but a trait - a struct, a primitive, a type parameter - is refused (E0316), and so is a trait object anywhere in an extern function's signature, whose C side has no table to call through (E0316).
  • A trait object has no fields: s.width is refused (E0204), whatever the value behind it holds.
  • delete of a trait object is refused (E0200): the size and destructor of the value behind it are not known where the object is used.

Type arguments. The trait's arguments are written after its name, virtual &Conv<i64>: its type parameters first, then its associated types, each in the order the trait declares it, so virtual mut &Iterator<i32> binds Iterator's Item. An object writes one argument for each of them, no more and no fewer (E0307: "Iterator takes 1 type argument in an object type, and 0 are written"). A generic function's parameter is inferred through an object's arguments as through an instantiation's.

Conversion. A value becomes an object wherever it is checked against a declared object type - an initializer, an assignment, an argument, a return, a struct-literal field, an array element: &c becomes a virtual &Shape or a virtual mut &Shape, as it becomes a &Circle or a mut &Circle parameter; a pointer c_ptr: Circle* becomes a virtual Shape*. The object holds the value's address and the table of its type's implementation of the trait. An object converts to no other object, a reference to no virtual Shape*, and a pointer to no borrowed object (E0200).

  • The value's type must implement the trait, at the object's arguments: its implementation's trait arguments and associated types must be the ones the object writes (E0315: "Square does not implement Shape, so &Square cannot become virtual &Shape"; "Square does not implement Conv<i32>, ..." for a Square implementing only Conv<i64>).
  • A value whose implementation is generic does not convert yet, nor does a value of a type parameter, whose implementation is not known in the generic body (E0315).

Supertraits. The table holds the methods of every supertrait of the object's trait as well, so the value's type must implement each of them, directly or through another supertrait (E0315: "Square does not implement Named, which Shape requires, so &Square cannot become virtual &Shape"). A supertrait's arguments are the ones the subtrait writes on it, with the object's arguments put in for the subtrait's own: an object of Sized<i64>, with type trait Sized<U> : Measure<U>, needs the value's type to implement Measure<i64>. An associated type of a supertrait written without arguments, type trait Twice : Pick, is bound by no object of the subtrait, and the value's type may bind it to anything.

Calls. s.area() on an object calls the method its trait or one of its supertraits declares under that name, through the table: the function of the value's type's implementation of the declaring trait at the method's position, or that trait's default where the implementation writes none, with the value's address as its receiver.

  • Which trait answers is the nearest, as for a bound: the object's own trait, then its supertraits in the order they are written, then theirs. An object of Label calls Label's area even when the value's type also implements an unrelated Shape declaring one, and a subtrait's own id is called before its supertrait's.
  • A method none of them declares is refused (E0358), and so is a type argument written on the call, s.area::<i32>(): a method callable through an object has no type parameters of its own (E0307, "Shape::area has 0 type parameters, and 1 type argument is written"), as on a call of any method that has no type parameters of its own (s.double::<i32>()).
  • A call through a reference or a pointer to an object, r.area() with r: &virtual &Shape or p: virtual Shape**, at any depth, calls through the object it reaches, as a call on the object itself does.
  • A call sees the method's parameters and return with the object's arguments put in: next through a virtual mut &Iterator<i32> returns Option<i32>, and convert through a virtual &Conv<i64> returns i64.

Object safety. A method is callable through an object only when its signature names the implementing type as its receiver alone: a &this or mut &this receiver, no type parameters of its own, and This in no other parameter and not in its return, except as the base of an associated type, This::Item, which the object's arguments bind. Any other method is refused at a call through an object (E0314), with the reason in a note, as is one whose signature names an associated type the object does not bind (a supertrait's, written without arguments); the object's other methods stay callable, and the table holds no function for it. A mut &this method is callable through either borrowed form, as a mut &T parameter accepts either reference.

An object satisfies its own trait. An object satisfies a bound of its own trait and of each of its supertraits, in every form: Keeper<virtual mut &Counter> is accepted where type struct Keeper<C> where C: Counter, and so is ident(o) with o: virtual &Counter where function ident<B>(b: B) -> i32 where B: Base and Base is a supertrait of Counter. No implementation is written for it; the compiler makes one, in which each method is called through the object's table, so a bounded call in the generic body reaches the implementation of the value behind the object, a default the table holds included, and one instance of Keeper<virtual mut &Counter> serves every type implementing Counter. An associated type of the trait, S::Item with S the object virtual &Source<i64>, is the object's own argument, i64.

  • Every method of the bound's trait and its supertraits that has no default must be callable through an object: a trait with such a method that has type parameters of its own, or names This outside its receiver, is satisfied by no object (E0306, "the trait bound virtual &Grow: Grow is not satisfied"). The note names the first such method and why a table cannot hold it, in the words a call of it through the object is refused with (E0314): "the method grow of Grow has no default, and cannot be called through virtual &Grow: it has type parameters of its own, and a table holds one copy of each method". No implementation can be written for an object, so the help does not ask for one: "use a type that implements Grow in place of virtual &Grow, or an object of a trait whose methods a table can hold". An object whose trait neither is nor inherits the bound's trait never satisfies it, and its help says so: "use a type that implements Size in place of virtual &Grow; an object implements only its own trait and that trait's supertraits".
  • A default the table cannot hold - map<B> and filter of Iterator<i32>, or take, which returns This - does not stand in the way: a bounded call of it is compiled once per object type, in the trait's module, with This the object, and each call on the receiver inside it goes through the table. So it.map(f) in function sum<I>(it: I) where I: Iterator<i32> runs with I the object virtual mut &Iterator<i32>, calling next of the value behind it.
  • The value behind an object a generic struct holds is borrowed, not owned, and nothing checks that it outlives the struct.

Overrides of such a default. The instance compiled for an object is the trait's default and nothing else: a type that writes its own map in its implementation of Iterator has it called directly and under a bound, but not through an object, which reaches the default. A default the table holds is not affected: its slot holds the type's own override where the implementation writes one.

null and comparison. null is a virtual Shape* where one is expected, both words null. A virtual Shape* compares with the null literal by == and !=, testing the value's address; it compares with nothing else, and a borrowed object does not compare at all (E0229).

The table's layout and the object's calling convention are in docs/abi.md; the symbols of a table and of an object type in docs/cryo-mangling-spec.md.