Cone Language Reference

Traits: interchangeable parts

On this page

A light socket takes any bulb of the right fitting, whatever its size, shape or colour. Code can work the same way: a function written against an interface takes any part that meets it, so behaviour is varied by handing in a different part, and a part is upgraded without touching what uses it. In Cone that interface is a trait. This page shows how to declare a trait, how a type meets one, how the choice of part is settled while the program is compiled or while it runs, enums as the closed form of the same idea, and the traits the library is built on. Afterwards you can write code against an interface and plug parts into it.

mod shapes;

import stdio use *;

// The interface: anything with these two methods is a Shape
trait Shape {
  pub fn area(self &) f32;
  pub fn perimeter(self &) f32;
}

struct Rectangle {
  height f32;
  width f32;
  pub fn area(self &) f32 { height * width; }
  pub fn perimeter(self &) f32 { 2. * (height + width); }
}

struct Circle {
  r f32;
  pub fn area(self &) f32 { 3.14159 * r * r; }
  pub fn perimeter(self &) f32 { 2. * 3.14159 * r; }
}

// Written once, for any Shape: &< is a virtual reference
fn describe(shape &<Shape) {
  print <- "area ", shape.area(), ", perimeter ", shape.perimeter(), "\n";
}

fn main() {
  let circle = new Circle(5.);
  describe(&circle);
  let square = new Rectangle(10., 10.);
  describe(&square);
}

It prints:

area 78.5397, perimeter 31.4159
area 100, perimeter 40

Try this: add a Triangle with its own area and perimeter, and pass it to describe, which does not change. Neither struct ever says it is a Shape: having the methods is enough.

A trait is the supertype here, and each struct that meets it a subtype. What makes that substitution safe, and the same rules at work for numbers, enums, functions and references, is the previous page, Subtyping; this one is about the trait itself.

Traits#

Note: All of this is implemented, except actor trait, whose spelling the grammar accepts and which the compiler does not build, as actors are not declared in source yet, and a requirement taking Self by value (below).

A trait is declared like a struct, with named methods and, possibly, fields:

trait Id {
  pub id u32;
  pub fn describe(self &) u32;
}

But it does a very different job. A struct specifies the complete layout and behaviour of concrete data. A trait abstractly specifies a pattern of behaviour (methods) and state (fields) that many concrete types share: Id says there are types out there with a describe method of this signature and a u32 field named id. So a trait differs from a concrete type in two ways:

  • No value's type is a trait. new Id(12) is refused ("'new' constructs a struct's value, and Id is not a struct"). You make a value of a concrete type, and view it through a trait it meets.
  • A trait's methods usually have no body, as describe has none. A call through the trait runs the method of the value's own concrete type.

A trait is an abstract kind#

trait is not a kind of its own but a modifier on a kind: a trait is the abstraction of whatever kind it modifies. The full spelling of the declaration above is struct trait Id { ... }. trait Id and struct trait Id are the same declaration, written two ways; the short form is the one you will normally write, and it means the struct family, because that is what a trait has always abstracted over.

Two more kinds take the modifier. A mod trait is the interface a module conforms to, with functions and globals as its members (below). An actor trait is the abstraction of an actor; the grammar accepts it so that its spelling is settled, and the compiler reports it as not built yet. The kind keyword says which family an abstraction belongs to, and composition requires the families to match: a struct composes a struct or a struct-trait, and an actor will compose an actor or an actor-trait. Nothing else on the declaration says which family it serves, and nothing about it is inferred.

enum does not take the modifier, and the omission is deliberate. An enum is concrete: its identity is the set of variants it declares, so anything you could hold behind an abstraction of one either is that set, and so is the enum, or is open, and so is a trait. enum trait is refused: "An enum is concrete: its variant set is its identity. Use 'trait' for an open abstraction."

Meeting a trait#

A concrete type meets (complies with) a trait when it declares everything the trait requires: a method of each required signature, and a field of each required name and type. Compliance is noticed structurally. A type that happens to declare what a trait requires may be viewed through that trait, whether or not it has ever heard of it, as Circle was at the top of this page. That is what lets you apply a trait to a library type you did not write, and it saves you from defining interfaces defensively in case you need them later.

Declaring it, with is#

A type may also say so in its own declaration:

trait Id {
  pub id u32;
  pub fn describe(self &) u32;
}

struct Node is Id {
  pub id u32;
  pub value u32;

  pub fn describe(self &) u32 {id + value;}
}

This is the only place in Cone where a subtype relationship is asserted rather than noticed. The compiler verifies the compliance where it is written, and refuses the declaration if it does not hold.

It is the same is you already know. p is Mobile asks of a value what struct Node is Id asserts of a type, and X is Y means "X is a Y" in both. The difference is the operand, not the question: a value's variant is dynamic, so the value form is answered when the program runs, while a type's relationships are fixed, so the declaration form is settled where it is written. One predicate over two kinds of operand, not two mechanisms that happen to share a word.

The obvious objection is that structural compliance makes the declaration redundant. It does not, for two reasons. First, a declaration forces compliance at the moment of declaring, rather than later at the point of use. If a type is meant to satisfy a trait, saying so is what makes the compiler tell you the day you break it, at the type itself, instead of the day something far away tries to view a value through the trait and fails there. Second, some traits are empty, and only a declaration can carry them: the marker traits, below. The two kinds of compliance exist alongside each other on purpose. Structural compliance keeps traits applicable to types that never anticipated them; is lets a type commit, and makes the empty trait expressible at all.

What the declaration takes, and what it gives#

  • A comma-separated list. A type may declare is against any number of traits: struct Ledger is Audited, Sealed, Move.
  • The trait contributes no fields. What a trait's fields state is a requirement, and the type declares them itself: the same names, the same types, in the trait's order, ahead of any field of its own. That prefix is what lets a plain &Id reference read the trait's fields straight out of the value, and what makes a same-size conversion to the trait a pure recast rather than a copy. Declaring them in another order, or behind a field of your own, is refused.
  • Only the first trait in the list may require fields, since only one set of them can occupy the front of the layout. The rest must require none, which is no restriction in practice, because a field two traits want is declared once and satisfies both.
  • A method the trait implements and the type does not is inherited free. A trait may give a method a body, which becomes the default for any complying type that does not declare its own. Declaring it is the primary case; taking the trait's implementation is the fallback. Either way the method is the complying type's own, called on a value or named through that type: Node.describe. The default may be generic (fn map[F fn(x T) U, U](self &, f F) ...): a call on the type makes an instance for it, with self the type.
    A type's own method replaces a shared one of the same name, whatever its shape (its parameters and result need not be the trait's): Zip declares its own zip that gives a flat triple, where Iterator's shared zip gives a pair, and the chunk cursors declare theirs to name their lifetime. This holds for a default that is generic, has a where clause or is inline, which fill no slot of the trait's virtual reference. A default that fills a slot must still be met by a method of its signature, as a requirement is. The type's own method is found by a direct call, and by generic code over the trait too (a generic function is made for the type it is given, so it.zip(other) in fn f[I Iterator[T], ...](it I, ...) calls the Zip's own when I is a Zip); only a type without its own gets the shared one. Behind a virtual reference (&<mut Iterator[T]) neither is reachable, since a generic default has no slot. A trait's method named through the trait, Id.describe, is an error, since the trait keeps no copy of it; only a trait's static functions are reached that way.
  • A trait may itself declare is against another trait, layering a deeper abstraction over a shallower one. It complies with the field requirement the way any type does, by declaring the fields.
  • It folds nothing. There is no use clause on an is: a trait is an abstraction, so there is no value for a folded name to be reached through. Delegation is what a field's own use clause is for.
  • An enum and its variants declare it too. enum Shape is Named gives every variant the trait's requirements and defaults, and a variant may name traits of its own beyond its enum: struct Square is Measured, written inside the enum. Neither list has a base, so neither may name a trait that requires fields; An enum that meets a trait has the details. A match on an enum may name such a trait as a pattern, which matches the variants that comply (Trait patterns).
  • Every name in the list is an open trait. An enum is closed: its variants are all declared inside it, so naming one anywhere in an is list, first or later, is refused. So is naming a struct: the base of an is is always an abstraction. A type may write extends and is together, in either order: they assert different things.
  • It is the one way to take a trait in. The retired mixin statement, which did the same from inside a type's body, is an error that points here.

Self in a trait#

Inside a trait, Self stands for the type complying with it. A requirement that takes another value of the same type

trait Same {
  pub fn same(self &, o &Self) bool;
}

struct Pt is Same {
  pub x i32;
  pub fn same(self &, o &Pt) bool { x == o.x; }
}

is met by a same method taking a reference to the complying type, o &Self or o &Pt within Pt, whether compliance is noticed by a generic constraint or declared with is. A default method a type takes from the trait means that type by Self too. Three things differ:

  • A virtual reference cannot be made to such a trait (What a virtual reference cannot reach): the value behind it may be of any complying type, so there is no one type for Self to be. &<Self is the exception, a virtual reference to the trait itself, met by a method taking &<Same.
  • An enum's requirement is met by each variant as it is written. A call through a reference to the enum reaches the variant by its tag, with an argument that may hold any variant, so Self there is the enum: a variant meets o &Self with o &Number.
  • Not built yet: a requirement taking Self by value, b Self or a by-value self, is refused at a trait whose implementations may differ in size, as a trait held by value.

Virtual references: choosing the part while the program runs#

Note: Borrowed virtual references are implemented. An owning one (Uni[Shape]) is built, dispatched through, lent as a borrowed one, and finalized and freed when its last owner goes, but a &<uni borrow still moves it. A method is dispatched only through a virtual reference whose permission grants what its self declares, but dispatching through a &<uni hands the method that reference itself, which moves it. A lifetime bound, &<Shape + 'a, is implemented on a borrowed virtual reference; an owning one takes none yet, and is bounded by 'static.

Sometimes which part will arrive is decided only while the program runs: a shape read from a file, an event from the user. describe at the top of this page takes a virtual reference, written &<Shape: a reference to some value whose structure (fields) and behaviour (methods) meet Shape, without knowing its concrete type. A plain reference to a Circle or a Rectangle given where one is wanted becomes one automatically, and describe calls the right area for each.

How it works#

A virtual reference is a "fat" reference: two pointers, one to the value and one to a vtable, a table describing the value's concrete type as seen through the trait. It holds the addresses of the type's methods that the trait declares, where the trait's fields sit, an id unique to the concrete type, and the type's size, alignment and finalizer, which an owning virtual reference needs to release the value. There is one vtable for each concrete type and trait, one for Circle-as-Shape and another for Rectangle-as-Shape. When a reference to a Circle is passed to describe, the virtual reference is made by attaching the Circle vtable to the pointer, and a call through it fetches the method from the vtable and calls it, handing it the pointer. Calling a method that way is called dynamic, or virtual, dispatch.

Working with virtual references#

A plain reference given to a variable or parameter that wants a virtual reference is coerced to one automatically, which is called upcasting. The variable's type may say so explicitly:

let virtref &<Shape = &circle;

A concrete type that does not structurally meet the virtual reference's type is a compile error there. The type may implement more fields and methods than the trait asks for, but it must implement at least the ones it does. Virtual references can be owning or borrowed, and are passed around, stored and compared like other references. But a virtual reference may not be dereferenced with *, since it may point at values of very different size and composition.

An owning virtual reference's type is written as any owning reference type is, Uni[Shape]: it is virtual because its value type is an open trait. It is made by coercing an owning reference, and it lends as any owning reference does (Borrowed references): where a borrowed virtual reference is wanted, the owner is borrowed from, not moved. Dispatching a method through it hands the method a borrowed virtual reference with the permission its self declares, so the owner may be used again after the call. The owner may not be replaced or moved while a borrow lent from it is still to be used, nor lent mutably to one call beside another lend or a move of it (app.mix(app), for a self &mut method, is refused):

var app Uni[Shape] = new Uni[Circle](5.);
app.area();                       // lends app to the method
let view &<Shape = app;          // borrows from app
describe(app);                    // and so does passing it

An owning virtual reference owns its value exactly as the owning reference it was coerced from did: that reference is moved into it (or, for a counted region like Rc, copied as one more owner), and when its last owner goes away (out of scope, replaced, or inside a struct that dies) the value is finalized as its concrete type and its memory is given back to its region. The vtable carries what that needs to know about the concrete type.

A virtual reference hides what the value it points at holds, borrows included. In a function's signature or a struct's field, a bound says how long those last: &<Shape + 'a points at a value whose borrows all last at least as long as 'a (lifetime bounds). The type then holds 'a beside its own lifetime, so what it carries goes, wherever it is stored and read back out, where an 'a borrow may; and a value coerced to it where it is returned or stored through a parameter may hold only borrows known to last 'a. &<Shape + 'static points at a value holding only global borrows. With no bound, a borrowed virtual reference holds its own lifetime only.

An owning virtual reference names no lifetime at all, so it may outlive any borrow: Uni[Shape] is bounded by 'static. A value holding a borrow that is not global may not be made one, since nothing after it would follow what that value holds:

struct Ring['a] is Shape {
  pub inner &'a Circle;
  pub fn area(self &) f32 { inner.area(); }
  pub fn perimeter(self &) f32 { inner.perimeter(); }
}

var app Uni[Shape] = new Uni[Circle](5.);   // Ok: a Circle holds no borrow
let c = new Circle(2.);
app = new Uni[Ring](new Ring(&c));       // ERROR: a Ring holds a borrow of c
Error 1249: An owning virtual reference ('Uni[Trait]', 'Rc[Trait]') names no lifetime, so it may outlive any
borrow: the value made one may hold only global borrows, as if bounded by ''static'. This one holds a borrow
of 'c' (made at 19:31), which belongs to this function.

The main use of a virtual reference is the dot operator, to dispatch the trait's methods and reach its fields, and its permission governs what it may do exactly as a plain reference's does: a method is dispatched through it only when the reference's permission grants what the method's self declares. A method taking self &mut needs a virtual reference that permits mutation (&<mut, &<uni, or an owner such as Uni[Shape]); through a &< or &<imm one, only methods taking self & may be called:

trait Growable {
  pub fn grow(self &mut);
}

fn enlarge(g &<mut Growable) {
  g.grow();                       // a mutable virtual reference may
}
fn peek(g &<Growable) {
  g.grow();                       // ERROR: g grants no mutation
}
Error 1056: No method declared by `grow` accepts the call's arguments.

The same holds from the other side. A call through the trait lends only the permission the trait's method declares, so a type's method may not ask for a stronger self than that: self &mut where the trait says self &, self &uni where it says self &mut, or self &imm where it says self &. Giving such a type as the virtual reference is refused, naming both methods. A weaker one is fine: a trait's self &mut is met by a method taking self &. A closure literal given as the trait is no exception.

trait Reading {
  pub fn read(self &) i64;
}

struct Counter {
  n i64;
  pub fn read(self &mut) i64 { n = n + 1; n; }
}

fn peek(r &<Reading) i64 { r.read(); }

var c = new Counter(0);
peek(&c);                        // ERROR
Error 1401: Counter's `read` takes `self &mut`, but Reading's `read` takes `self &`: a call through the
trait lends only what the trait declares, so Counter would have more of the value than the caller allowed.
Declare `read` with `self &`, or the trait's with `self &mut`.

Getting the concrete type back: match#

Sometimes you want the concrete reference back from a virtual one, which is called downcasting. A match does it, each case naming a concrete type and binding a plain reference to it:

fn which(shape &<Shape) i32 {
  match shape {
    case let c &Circle { 1; }       // c is a &Circle here
    case let r &Rectangle { 2; }    // r is a &Rectangle
    else { 0; }                     // some other shape
  }
}

The vtable records which concrete type made the virtual reference, and the match checks it. So each pattern names a concrete type: a trait or an enum is refused there, since no value is created as one. A virtual reference made from a reference to an enum was made from one of its variants, and it is the variant that a pattern names. A trait is a pattern only in the other direction, a match on an enum, where it names the variants that comply with the trait and binds a virtual reference built from one (Trait patterns); an open trait has no such pattern.

What a virtual reference cannot reach#

Some traits cannot be the type of a virtual reference. That is so if any method the trait requires is generic: a vtable slot holds one signature, and a generic method has one per instance, so the compiler refuses the virtual reference and names the method. A generic method that is private, or a generic function with no self, is dispatched through nothing and so costs the trait nothing, and neither does a generic default (a method given a body), which is cloned into each type that declares the trait and is reached through that type, never through a slot. A default that is inline, or exists only where a where condition holds, has no slot either. It is also so if the trait uses Self in any form other than as a virtual reference (&<Self).

The vtable holds only the trait's public methods and fields: a private one is not a requirement and has no slot, so it cannot be reached through a virtual reference, even from the trait's own module, where its name is visible. The compiler refuses the use and names the member and the trait; declaring it pub in the trait gives it a slot. The virtual reference itself is not refused, and a private method is still reached through self in the trait's defaults and on the concrete type.

A closure written where a trait is wanted#

A trait whose work is one method, such as a distance function or a comparison, would need a struct written for each use. A closure literal given where such a trait is wanted does that instead: it is the method. The trait must have exactly one required method, a method written without a body, and no field. A method with a body is a default, and the literal's type inherits each default as any type complying with the trait does, so the defaults call the literal's body through the required method:

struct V {
  pub x f32;
  pub y f32;
}

trait Shape {
  pub fn distance(self &, p V) f32;                          // the one required method
  pub fn nearness(self &, p V) f32 { 100. - self.distance(p); }   // a default
}

fn depth[S Shape](shape &S, p V) f32 { shape.distance(p) * 2.; }
fn lend(shape &<Shape, p V) f32 { shape.nearness(p); }

let p = new V(3., 4.);
depth(q => q.x * q.x + q.y * q.y - 5., p);     // the static form: 40
lend(q => q.x + q.y, p);                       // the dynamic form: 100 - 7: 93
let k = 10.;
let own = new Uni[Shape](fn(q V) [off = k] f32 { q.x + off; });
own.distance(p);     // 13
own.nearness(p);     // 87

The places a trait is wanted are a generic constraint ([S Shape], taken as S, &S or &mut S), a borrowed virtual reference (&<Shape, &<mut Shape, which lend the literal for the statement), and an owner made with new (Uni[Shape], Rc[imm, Shape]). It applies only to a literal, written at the point the trait is expected, so no struct of yours meets a trait any differently. A generic trait's instance as the constraint ([S Shape[T]]), and a literal as a field's initial value or a variable's, are not built.

  • The signature is the method's, without its receiver. The literal's parameter types may be left out, and if written they must be the method's exactly, as must a written return type, or the error names the method: "the trait's distance takes V there". The number of parameters must match.
  • The literal's method has the method's name, so a state entry or captured variable of the same name is the ordinary duplicate-name error.
  • A trait with two required methods, none, or a field is refused, naming what it has. A trait with only defaults has nothing to fill.

A literal that changes state. As for any closure, the method's self follows the body: self & for a body that only reads, self &mut for one that changes what it holds or borrows. The literal is held to the trait as any implementer is, and may not ask for a stronger self than the trait's method declares. So a literal that changes state fills a trait whose method says self &mut, and is refused where the method says self &, under every holder, &<Shape, &<mut Shape, Uni[Shape] and a generic's S alike. A literal that only reads meets a method of either kind.

trait Counter {
  pub fn bump(self &mut, step i32) i32;
}
fn bumpTwice(c &<mut Counter) i32 { c.bump(1); c.bump(10); }

bumpTwice(fn(step i32) [var n = 5] i32 { n += step; n; });    // 6, then 16: 16

var total = 0.;
depth(q => { total += q.x; total; }, p);    // ERROR: Shape's distance takes 'self &'
Error 1401: This closure's `distance` needs `self &mut` (it changes state it holds or borrows), but Shape's
`distance` takes `self &`: a call through the trait lends only what the trait declares, so the closure
would have more of the value than the caller allowed. Declare the method `self &mut` in the trait, or
don't change that state.

This is a rule about the reference, not only the trait: a literal that changes state, given as &<mut Shape and narrowed to a &<Shape, would change state through a reference promised to read.

Enums: a closed set of subtypes#

A trait is open: any type, anywhere, may meet it. An enum is the closed form of the same idea. Its variants are its subtypes, all declared inside it, and a value or a reference of any variant stands wherever the enum is wanted. A method the enum declares without a body is a requirement every variant implements, and a call through a reference to the enum runs the variant's own, chosen by the tag the value carries. An enum may also say that it meets traits, which then reach every variant. And a value of the enum can be tested and narrowed to the variant it holds. This section has all four.

Members every variant shares#

An enum may declare members in common: fields every variant has, and methods every variant answers. They are what let a caller do useful work with a value whose variant it has not yet recovered.

enum Event {
	pub time i64;                                  // a field every variant has

	pub fn stamp(self &) i64 {time;}               // a method with a body: a default
	pub fn describe(self &) i32;                   // a method without one: a requirement

	pub struct ButtonEvent {
		pub button u8;
		pub pushed bool;
		pub fn describe(self &) i32 {1;}
	}
	pub struct KeyEvent {
		pub key char;
		pub fn describe(self &) i32 {2;}
	}
	pub struct QuitEvent {
		pub fn describe(self &) i32 {3;}
	}
}

let k Event = Event.KeyEvent(17, 'q');
(&k).stamp();       // 17
(&k).describe();    // 2, KeyEvent's own

All three variants begin with the time field, ahead of any fields they declare themselves. A method with a body is a default, which each variant gets unless it declares that name itself; a method without one is a requirement, which every variant must implement, and calling it through a reference to the enum dispatches on the variant (below). Either way, what a variant has is its own copy of the method. It is called on a value, event.stamp(), or named through the variant, Event.KeyEvent.stamp. Naming it through the enum, Event.stamp, is an error: the enum keeps no copy of its methods, only of its static functions.

A common member may be written anywhere in the body, before or after the variants. What makes a member common is its kind, not its position: a struct is a variant, and anything else is common. A common method applies to every variant but reaches only the enum's common fields, and the layout puts the common fields first in every variant.

Method dispatch#

When every variant should answer the same method with its own implementation, the enum declares the method without a body and each variant implements it:

enum Number {
	pub fn double(self &mut Self);

	pub struct Real {
		pub n f32;
		pub fn double(self &mut Self) {
			n *= 2.;
		}
	}

	pub struct Complex {
		pub r f32;
		pub i f32;
		pub fn double(self &mut Self) {
			r *= 2.;
			i *= 2.;
		}
	}
}

Calling number.double() on a reference to a Number runs the implementation of whichever variant the reference points at: the tag says which variant it is, that selects the variant's vtable, and the vtable holds the method. The same goes for any other trait the enum complies with. A reference to a Number may be passed where a virtual reference to such a trait is expected, and the virtual reference it becomes carries the vtable of whichever variant it points at, chosen by the tag, exactly as a reference to that variant would. Narrowing it with is or match therefore names a variant, never the enum. The enum need not comply as a whole for a match to use a trait: see Trait patterns.

A method may also take self by value, and a variant's value calls its own copy:

enum Reading {
	pub id i32;
	pub fn get(self) i32 {id;}
	pub struct Low {}
	pub struct High {pub n i32;}
}

Reading.High(1, 4).get()      // 1: High's own copy of get

Inside a copy, Self is the variant, in the receiver, in a parameter and in the result. A method written pub fn setN(var self, v i32) Self {n = v; self;} is, in High, setN(var self High, v i32) High: calling it on a High gives a High, with its own fields. The enum requires nothing of the copy itself. A variant may declare its own method of the same name, which replaces the copy; it must then have the enum's signature with Self read as that variant, as an override has the signature of what it overrides, so a different parameter or result is refused.

Not built yet: on a value typed as the enum itself, which variant's copy runs would have to be read from the tag the same way, and that is not built for a value. Calling such a method on a value of the enum is an error today; match to recover the variant, or declare the method with a reference receiver, which dispatches as above, or name the enum as the receiver.

One function over the whole enum#

The receiver a method names decides which kind it is. A method written with self & or self &Self belongs to the variants: it is copied into each, and inside a copy self is that variant. A method whose receiver names the enum (self &Lamp, self &mut Lamp, any other kind of reference, or by value self Lamp) is one function over the whole enum. It is not copied, nothing is dispatched on the tag, and inside it self is the enum, so match self, case is and self is Red work as they do anywhere else:

enum Lamp {
	pub n i32 = 7;
	Red;
	Green;
	Off;

	pub fn describe(self &Lamp) i32 {      // one function over the whole enum
		match self {
			case Red   {10;}
			case Green {20;}
			case Off   {30;}
		}
	}
	pub fn next(self &mut Lamp) {          // may store any variant through self
		match self {
			case Red   {*self = Lamp.Green;}
			case Green {*self = Lamp.Off;}
			case Off   {*self = Lamp.Red;}
		}
	}
	pub fn get(self &) i32 {n;}            // copied into each variant, as above
}

let l Lamp = Lamp.Green;
l.describe()               // 20
Lamp.Red.describe()        // 10: a variant's value calls it, widened to Lamp

A method over the whole enum is called on a value of the enum or on a value of one of its variants, which is widened to the enum for the call, and by value it takes a copy. A variant's value cannot lend &mut Lamp, since the method could store another variant in it: a var variable that starts as a variant is the enum already (var l = Lamp.Red, below), and a variable of another kind declares it, var l Lamp = Lamp.Red. If a variant declares a method of the same name, a value typed as that variant calls the variant's own, and a value typed as the enum calls the enum's. Within one enum a name is declared once, so the two kinds cannot share a name there. A package that declares such a method defines it once, and an importer calls it. An enum that extends another does not inherit its base's methods over the base, since a value of the extension is not a value of the base. A generic enum's method over the whole enum, self &Option[T], is not available yet. Inside a variant, Self names the variant, so a variant holding the enum writes the enum's name: Branch(List[Tree]), not List[Self].

An enum that meets a trait#

An enum may say in its declaration that it complies with one or more open traits, exactly as a struct does, with is after its name. What the trait requires and what it gives then reach every variant: a default the variant does not declare becomes its own copy, and a requirement is met by the variant's own method or by one with a body the enum gives every variant.

trait Named {
	pub fn label(self &) i32;
	pub fn loud(self &) i32 {label() * 100;}
}

trait Measured {
	fn size(self &) i32 {7;}
}

enum Shape is Named {
	pub k i32;
	pub fn label(self &) i32 {k;}    // meets Named's requirement for every variant

	pub struct Circle {
		pub r i32;
		pub fn label(self &) i32 {r * 10;}
	}
	pub struct Square is Measured {        // a variant may name traits of its own
		pub s i32;
	}
}

fn loudOf(n &<Named) i32 {n.loud();}
fn shapeLoud(s &Shape) i32 {loudOf(s);}   // passes the variant's vtable, chosen by the tag

loudOf(&Shape.Circle(1, 7))     // 7000: Circle's own label, Named's loud
loudOf(&Shape.Square(2, 3))     // 200: the enum's label, Named's loud

A variant may name traits of its own in the same way, beyond its enum: struct Square is Measured above. Its enum is its base without being written, and naming the enum in the list is an error, since a variant is already a member of the enum it is written inside. An enum's list and a variant's name only open traits, and none of them is a base in the sense a struct's first one is: the enum lays out itself and every variant, discriminant first, so a trait either one names may not require fields. Nor may any is list name an enum, whether first or later: an enum's variants are all declared inside it, and nothing outside may join the set. An enum that extends another takes its base's traits with the variants it copies and names none of its own.

Trait patterns#

In a match on an enum, a pattern may name a trait instead of a variant. It matches every variant that complies with the trait, by declaring it with is or by having what it requires, including what the enum gives every variant. A variant that does not comply, Third below, is left to the cases after it. The match compiles to tests of the tags of the variants that comply, so it costs nothing beyond them, and it counts toward covering every value as the variants it names, so this match needs no else:

trait Measured {
  pub fn size(self &) i32;
}

enum Shape {
  struct Circle is Measured { pub r i32; pub fn size(self &) i32 {r * 10;} }
  struct Square is Measured { pub s i32; pub fn size(self &) i32 {s * 100;} }
  struct Third { pub t i32; }
}

fn measure(s &Shape) i32 {
  match s {
    case let m Measured { m.size(); }   // Circle's or Square's own size, by their vtables
    case is Third { 7; }
  }
}

The binding is a virtual reference to the trait, built from the variant the matched reference points at, so a call through it goes through that variant's vtable. As for a binding to a variant (match on variants), the matched value supplies the kind and the permission: on a &mut Shape, m is a &<mut Measured and may call what needs self &mut; on a &Shape it is read-only. It is a reference into the variant, so it follows the collection rule as a binding to a variant does, and it may not outlive the value it borrows. Because only a borrowed reference can lend a virtual reference, a pattern that binds a trait needs the matched value to be one (match &s, match &mut s); a value or an owner such as an Rc can be asked, case is Measured, and not bound. A pattern may not be written as a plain reference to the trait (&Measured), which has no vtable to carry. The patterns work in a bound if, let m Measured = s, in let-else, and joined by or and guarded like any other.

Which variants comply is decided as a generic's where T is Measured decides it: by declaring the trait, or structurally for a trait that requires something. A marker trait, which requires nothing, is complied with by declaring it alone. A method written over the whole enum (fn size(self &Shape)) is one function that no variant has a copy of, so it makes no variant comply; a trait that no variant complies with is an error, since the pattern could never match. Only a closed family can say which of its values comply, so an open trait has no trait patterns: a &<Measured can be narrowed to the concrete type it was made from, and only to that (Getting the concrete type back).

Narrowing to a variant: is and flow typing#

A test of a variant tells the compiler what the tested name is, and it uses the name that way until something may have changed it. After if s is Circle, inside its block s is a Circle wherever a field or a method of Circle is named on it: s.radius reads it, s.radius = 2 writes it, s.grow() calls it. This is flow typing, the guard that brings a value from the enum back down to its subtype:

enum Shape {
  struct Circle {
    pub radius i32;
    pub fn grow(self &mut) { radius += 1; }
  }
  struct Rect {
    pub w i32;
    pub h i32;
  }
}

fn area(s &Shape) i32 {
  if s is Circle {
    return 3 * s.radius * s.radius;   // s is a Circle in this block
  }
  s.w * s.h;                          // and by elimination a Rect after it
}

fn big(s &Shape) bool {
  if s is Circle and s.radius > 3 { true; } else { false; }
}

fn kind(s &Shape) i32 {
  if s is Circle { 1; } elif s is Rect { 2; }   // names every variant: a value with no else
}

The name is narrowed:

  • in the block of an if, or the case of a match that names the variant, and on the right of an and whose left tests it, as in big;
  • after a branch that leaves, for what follows: after if s is Circle { return ...; }, s is not a Circle;
  • by elimination: when the variants that remain are one, s is that one, in the else of a two-variant test, in the last case of a match, after an early exit, as in area;
  • by an assignment of a known variant: after state = State.Run(speed), state.speed reads the field.

Not followed yet: the block of an elif (or else if) whose own test names a variant is meant to be narrowed too, and today it is not (elif state is Run { state.speed ... gives 1024, "Method or field `speed` not found"). Test with a separate if, or match.

An if used as a value needs its else, since every path must give a value. But an if chain of is tests that names every variant of an enum is a value without one, as kind is; unlike a match, which must cover every value, the chain is not asked to name them all.

It is a name that is narrowed (a local variable or a parameter, held by value or through a reference), and only to name a member of the variant. Narrowing does not retype the name for anything else: it is still the original, whole value, passed and returned as the enum, and takesCircle(s) is still an error (bind it for that). A variant declared as a type, Ok(i32);, has no fields to name: the narrowed name has nothing more to offer, and a binding is how to reach what it holds. The test if x is a plain truth test, and never narrows: only the language's own tests do, is and a binding, never what a method returns (Option unwraps with them).

A narrowing ends when the name may hold another variant: an assignment to the whole of it, a mutable borrow of the whole enum (a call that takes &mut Shape), a swap, a reference stored over (*s = ...), or a path where one branch did one of these. Writing a field within the variant is not a change, whether a field of the variant or a field the variants share. After a change the name is its whole type again, and a use that needed the variant says so, at the use, naming the change:

enum Machine {
  struct Stop {}
  struct Run { pub speed i32; }
}

fn step(state &mut Machine) {
  if state is Run {
    state.speed = state.speed + 1;   // Ok
    *state = Machine.Stop;          // the whole value is replaced: the narrowing ends
    state.speed;                    // ERROR
  }
}
Error 1460: `state` is not known to be Run here, and `speed` is a member of Run: it was changed at 9:5. Test
it again with 'is', or bind it with 'if let x Run = state'.

A name that is a reference others may share (& or &mut) is narrowed through a reference the compiler declares where the test was made, and the loan walk follows it as it follows a binding (Borrowed references): a call handed another name for the enum, a store through one, a swap, or a call whose body it cannot read is a change, refused at the next use of the narrowed name; a call that cannot reach the enum is none. Nothing follows the reference between the test and the block's start (or, inside one condition, the use), so a call or a store made in a later operand of the condition ends the narrowing, whatever it reaches: if s is Circle and toRect(t) and s.radius > 0 is refused at s.radius, naming the call. A name held by value, or through an imm, uni or mut1 reference, has no other name that can change it, so only its own assignments and mutable borrows are changes.

A narrowing from before a loop is good for the loop's first pass. A use of it that a change later in the loop reaches again by the loop's back edge is an error, reported at the use. Test again inside the loop to narrow afresh, which is how a state machine reads its next state:

enum State {
  struct Idle {}
  struct Run { pub speed i32; }
}

var state State = State.Idle;
while running {
  if state is Idle { start(); state = State.Run(1); }   // tested each pass
  if state is Run { state.speed = state.speed + 1; }    // narrowed by its own test
}

A var variable, parameter or field whose type is inferred from a variant holds the variant's enum, so another variant can be stored in it later, a &mut of it goes where a &mut of the enum is wanted, and match and is ask which variant it holds: var st = Status.Done is a Status. The variant's fields stay reachable by name until the variable changes (var c = Shape.Circle(5); c.radius). A let keeps the variant's own type throughout, and so do a global, a static and a variable of an @unsized enum. A declared type is never changed, and neither is a var pattern binding, which is the matched value's own storage under its variant's name. A generic enum's variant whose type arguments the value fixes gives its instance: var o = Some(5) is an Option[i32], while var o = None is refused for want of the type. A method only the variant has is reached by name (c.grow()) but not through a borrow of the variable until it is declared as the variant, var c Shape.Circle = ....

Open traits: variants other modules may add#

Note: All of this is implemented.

An enum declares a closed set of variants: the whole set is written inside its declaration, and nothing elsewhere can add to it. Sometimes that is exactly what you do not want. Where other modules should be able to add variants of their own, the set cannot be enumerated anywhere, so a trait, and the structs that declare is against it, express it.

How an open set is written#

The variants are declared separately from the trait rather than inside it, and may live anywhere, even in a different module. Each says which trait it belongs to by declaring that it is one. A trait contributes no fields, so each variant declares the trait's own fields itself, first and in the trait's order, which keeps the trait's layout a prefix of every variant's. Being declarations of their own, the variants are names of the module that declares them, as any struct is: KeyEvent, where an enum's variant is a name of its enum, Event.KeyEvent, until a use folds it into a module.

trait Event {
	pub time i64;
}

struct ButtonEvent is Event {
	pub time i64;
	pub button u8;
	pub pushed bool;
}

struct KeyEvent is Event {
	pub time i64;
	pub key char;
}

struct QuitEvent is Event {
	pub time i64;
}

fn when(e &<Event) i64 { e.time; }

What openness costs#

Because the variants are open-ended, the compiler cannot know when the program is compiled how many there will be, and so cannot give each one a guaranteed-unique number. An open trait therefore has no tag field, implicit or explicit. Everything an enum does with its tag has to be done another way. Without a tag there is nothing in the value to say which variant it is, so neither pattern matching nor method dispatch works on a plain reference to the trait. Both need a virtual reference, which carries a pointer to the variant's vtable alongside the pointer to the value. That is where the cost lands:

  • A virtual reference is a fat pointer, twice the size of a regular reference.
  • A match has to check the possibilities one at a time, because there is no number to index with. An enum's tag is an index, so a closed set is not only exhaustive, it is faster.
  • Variants differ in size, so no value of the trait may be held and nothing can swap one variant for another in the same storage. Pool allocation, which wants one size, is out.

Choosing between an enum and an open trait#

Choose an open trait only if you really need other modules to be able to add variants. Otherwise choose an enum, which is closed, tagged, exhaustively matchable, and by default holdable by value. The middle case is a closed set whose variants differ enough in size that padding them all to the largest is not worth it.

That is an enum too, written enum @unsized: it keeps the tag, the exhaustive match and the fast dispatch, and gives up only the value semantics, so it is reached only through a reference, as an open trait's variants are. It is for a set that is genuinely closed, wants exhaustive matching, and has variants whose sizes differ enough to matter; graphs and trees, reached by reference anyway, are usually better served by an open trait. Memory has the details: An enum reached only by reference: @unsized.

Marker traits: facts about a type#

Plenty of useful traits carry no fields and no methods at all: they are testable invariants, adjectives a type either claims or does not. There is no such thing as a structural match to a trait that declares nothing (every type would match it, so the match would say nothing), so such a trait can only be carried by the types that nominate it, which is what is is for. Nothing else in the language can express it. You may declare marker traits of your own, trait Audited {}, and constrain a generic on them.

The compiler knows these marker traits of its own, reached from every module without an import, and none has members:

  • Move and Copy. Every type is exactly one of the two. Unlike a marker trait you write, the compiler also grants one to every type from what it can see: a finalizer, a field that moves or an owning reference that cannot be aliased makes a type Move, and a type with none of these is Copy. A type may still nominate either. struct Handle is Move makes a type move that would otherwise copy; is Copy asserts that it copies, and is refused on a type that turns out to move, including one that also says Move.
  • RegionRef, which a region's reference struct declares (Regions). A region ref may also declare Move, as Uni does: struct Uni is RegionRef, Move.
  • AtomicValue, which marks a value changed only by atomic operations, even where it is let: core's Atomic[T] declares it (Atomic values). A type declaring it moves.
  • Integer, which the compiler grants to the integer types, i8 to i64, u8 to u64, isize and usize, for a generic to constrain on: where T is Integer.
  • Pointer, which the compiler grants to the raw pointer types, *T whatever T, and not to a reference, for a generic to constrain on: where T is Pointer.
  • Sized and DynSized, granted to a type by its size, and Immutable, declared and never granted (Generic constraints has these three).
  • Sendable, which the compiler grants to every type whose values may cross to another thread, for code that hands a value to one to constrain on: thread.start and the sync package's channels ask where T is Sendable. What it refuses is in Race condition safety. A type holding raw pointers it shares safely may declare it, which the compiler takes on trust; an instance of a generic type declaring it is Sendable only where its type arguments are.
  • Shareable, Sendable's sibling, which the compiler grants to every type a borrow of which several threads may hold at once: no counted owner whose count is not atomic, no traced reference, no &mut and no unmarked reference to a trait or a callable in it, where a borrow of it reaches. A parallel each body may name only such values from outside its loop, and a generic constrains on it, [F fn() + Shareable] (Sendable and Shareable). A type may declare it, taken on trust, like Sendable. A reference to a trait or a callable may say what the value behind it is: &<Shape + Shareable, Uni[fn() + Sendable].
  • ShapeKeeping, ShapeChanging's twin, which the compiler finds of the methods a virtual reference calls and not of a type: they reshape no collection they reach through a path others share, so a call through &<mut Scorer + ShapeKeeping keeps the caller's borrows into one (Borrowed references). It is written only after a + in the type of a virtual reference, and a type cannot declare it (a package's include file records it on its types).

Their names are taken in every module, as a built-in type's are: a module-level declaration named like one, such as struct Integer, is an error saying the name is a built-in trait's.

These are traits rather than attributes because each is a fact other code may ask about or constrain on. An attribute, such as @opaque, is an instruction to the compiler about representation or linkage that no code asks about. A generic asks with a constraint, where T is Copy (Generic constraints), which a marker answers only by the compiler's grant or a declaration, and any other trait also by a type that fits it. (The other asking side, a test while the program is compiled, is not built yet.)

Traits the library is built on#

A few of core's traits name the shapes that the collections and much else are written against. A type that meets them works with every collection and every algorithm written over them.

Hash, and the hash the compiler supplies#

A key of a table, or anything else looked up by what identifies it, says what identifies it by feeding a Hasher, and the Hasher turns what was fed into a number. The two are kept apart: a type decides what it feeds, and whatever holds the keys decides the algorithm, so changing the algorithm changes no key type. Both are core's, reached from every module without an import. The trait is

pub trait Hash {
  pub fn hash(self &, h &mut Hasher);
}

and the rule that ties it to equality is that if two values are == they feed a hasher the same things. So a type that declares is Hash must have a ==, and is refused without one. A struct that declares Hash and writes no hash is given one by the compiler, as != is derived from ==: it feeds each field, in the order declared.

struct Id is Hash {
  pub slot u32;
  pub gen u32;
  pub fn `==`(self, o Id) bool {slot == o.slot and gen == o.gen;}
}

let id = new Id(1, 2);
var h = Hasher.seeded();
id.hash(&mut h);          // feeds slot, then gen
let number = h.finish();

It feeds the fields, never the struct's bytes, because the bytes between fields are undefined. It is refused, at the field, where a field has no hash that agrees with its ==: a float (NaN is not equal to itself, and -0 is equal to 0), or any type that is not Hash, such as a struct that does not declare it, a reference, a slice, a tuple, an array or a pointer. A type that wants such a field in its key writes its own hash, which feeds what identifies it:

  pub fn hash(self &, h &mut Hasher) {
    h.writeU64(u64.from(slot) | (u64.from(gen) << 32u64));   // two halves, mixed once
  }

Every integer type and bool is Hash, feeding its bits, and so is an owner of text, a Uni[str] or an Rc[str], feeding the bytes as the str it lends does; a float is not. A struct may hold such an owner as a field and still be given its hash, which feeds the field as the str it lends. A value whose length varies feeds a terminator after its bytes, h.writeStr(bytes), which is the bytes then the byte 0xFF, so that where one ends and the next begins is part of what is fed: ("ab", "c") and ("a", "bc") differ as the two parts of one key. A fixed-size run of bytes is h.writeBytes(bytes).

The Hasher is a fast hash of the wyhash family: each word fed is xored into its state and multiplied (mulFold), and bytes go through wyhash itself. Hasher.seeded() starts it from a seed drawn at random from the operating system once for the process (processSeed() is that number), so which keys collide is different on every run and no one outside can tell: the defence against keys chosen to collide. Hasher.withSeed(n) starts it from a seed of your own, and then the same keys give the same numbers on every run and build, for a table whose order must be reproducible.

Iterator and Iterable#

Two more of core's traits name the shapes a sequence of values is walked by, for code that is generic over them. A type is an Iterator[T] by having next, and an Iterable[T, I] by having iter. The cursors of str and of every collection have them, and so may a struct of your own (Method patterns shows how):

pub trait Iterator[T] {
  pub fn next(self &mut) Option[T];     // the next value, None once there are no more
}

pub trait Iterable[T, I] where I is Iterator[T] {
  pub fn iter(self &) I;                // a cursor over what it holds, without consuming it
}

A function asks for one as a bound, decided where it is called and compiled for the type's own methods, or as a virtual reference (&<mut Iterator[i64]), which takes any cursor through one compiled function:

fn count[T, S Iterator[T]](it &mut S) usize {
  var n = 0usize;
  while true {
    match it.next() {
      case let v Some {n = n + 1usize;}
      case is None {break;}
    }
  }
  n;
}

fn total[S Iterator[&i64]](it &mut S) i64 {   // a cursor that lends numbers
  var sum = 0i64;
  while true {
    match it.next() {
      case let v Some {sum = sum + *v;}
      case is None {break;}
    }
  }
  sum;
}

fn walk[T, I, C Iterable[T, I]](c &C) usize where I is Iterator[T] {
  var it = c.iter();
  count[T, I](&mut it);
}

T is what next gives, so a cursor that lends its values (a Deque's items(), a Dict's keys()) is an Iterator[&V]. The &V of a bound names no lifetime, and a cursor whose next returns the lifetime its own struct declares (Option[&'a V]) meets it, as a method with no other parameter has nothing to store through its receiver. The cursor's own signature is what a bound's call reads, so borrows given by two calls of next may be held together; through a virtual reference the trait's signature is read, which ties a result to the whole cursor. Iterable takes its cursor's type as a second argument, I, because a trait cannot yet name a type its implementer chooses; both arguments are found from the type passed. A type with no next (or iter), or with one that gives another type, is refused where the function is called.

Fitting is by shape, and the trait's shared methods come with the declaration. A type that says struct Countdown is Iterator[i64] has, beside its own next, the steps that work on a whole sequence, map, filter, take, skip, fold, any, all, count, find, sum, min and max (the adapters), which the trait writes once as generic defaults. An adapter is one of these steps: it takes a cursor and gives a cursor, or an answer. A type with a next that does not declare the trait meets a bound on it all the same, and each walks it, but it has none of the shared methods: asking it for map is an error that says to declare it. A generic default has no slot behind a virtual reference (&<mut Iterator[i64] calls next), and a default that exists only where a condition holds (sum wants number items) has none either.

struct Countdown is Iterator[i64] {
  pub n i64;
  pub fn next(self &mut) Option[i64] {
    if n == 0 { None; } else { n = n - 1; Some(n + 1); }
  }
}

var cd = new Countdown(4);
cd.map(x => x * 10).sum();      // 100: 40 + 30 + 20 + 10

Each step of a chain is a cursor of its own type (the whole chain's, so a chain is held in a variable of inferred type, or walked at once), and the terminal methods take the cursor by &mut, so one called on a chain made in the same expression needs no name for it. The closures are generic arguments ([F fn(x T) U]), each its own type, so a chain is compiled into one loop with every closure's body inline and no call through a pointer for any item. A closure that changes a variable (x => { n += 1; *x; }) is run once for each item the chain asks for and no more. Over a cursor that gives numbers (not a borrow), a map and a sum is vectorized as the hand-written loop is. Over a cursor that lends, the loop is not vectorized, since each step answers an Option of a borrow, which is a pointer tested for null: the same loop a hand-written while over next() is.

The collection traits#

The collections package declares five small traits, one for each thing a collection can do, so that a function asks for exactly the ability it uses. Each is met structurally, by having the methods, so none of the collections declares one:

  • Collection (len, isEmpty: List, Deque, String, Dict, OrderedDict, Set);
  • Indexed[T] (get(i): List, Deque);
  • Stack[T] (push and pop at the back, peek: List, Deque);
  • Queue[T] (push at the back, popFront: Deque);
  • Map[K, V] (get, insert, remove, contains by a &K: Dict, OrderedDict).

A function asks for one as a static bound (A generic trait as a constraint), fn drain[S Stack[i64]](s &mut S), fn drain[T, S Stack[T]](s &mut S) or fn drain[T, S](s &mut S) where S is Stack[T], called with &mut list or &mut deque; the bound is decided where the function is called, so the call is to the type's own methods. A trait is given all its type arguments ([S Stack] is refused), while Collection, which has none, is written alone, fn count[C Collection](c &C). The other form is a virtual reference, fn drain(s &<mut Stack[i64]), which takes the same arguments through one compiled function and a table of methods, for a value whose type is not known where the function is compiled; every trait works either way. Map's insert answers the old value, an Option (None for a new key). A Set's insert answers whether the element was new.

mod abilities;

import stdio use *;
import collections use *;

// A function asks for exactly the ability it uses
fn countOf[C Collection](c &C) usize { c.len(); }

fn drainSum[S Stack[i64]](s &mut S) i64 {
  var total = 0i64;
  while true {
    match s.pop() {
      case let v Some {total = total + v;}
      case is None {break;}
    }
  }
  total;
}

// The other form: one compiled function, called through a table of methods
fn peekTop(s &<mut Stack[i64]) i64 {
  match s.peek() {
    case let v Some {*v;}
    case is None {-1i64;}
  }
}

fn main() i32 {
  var list = List[i64].empty();
  list <- 1i64, 2i64, 3i64;
  var deque = Deque[i64].empty();
  deque <- 10i64, 20i64;
  let word = String.from("four");
  print <- countOf(&list), " ", countOf(&deque), " ", countOf(&word), "\n";
  print <- peekTop(&mut list), "\n";
  print <- drainSum(&mut list), " ", drainSum(&mut deque), "\n";   // a List and a Deque are Stacks
  0i32;
}

It prints:

3 2 4
3
6 30

Functions as interchangeable parts#

At the function's layer the interface is a signature, and the subtypes are the functions and callables that have it: a function reference takes any function of the signature, and a closure reference any callable of it, whatever state it carries. Those, with what a closure is as a part, have a page of their own, next: Function and closure references.

Module traits: a module that plugs into a framework#

A module may conform to a module trait, taking the trait's defaults it does not declare. The framework traits a program plugs into, whose entries would be configurable, are not implemented.

A program plugs into a larger framework, a shell that runs it, a web server that hands it requests, and the framework says what every program plugging into it must provide. That interface is a module trait: the abstraction of a module, as a trait is the abstraction of a struct. It declares functions and global variables. One written with a body, or with an initial value, is a default; one written without is a requirement:

mod hosts;

pub mod trait Runner {
  pub fn step(n i64) i64;        // required
  pub var scale i64;             // required

  pub var offset = 100i64;       // a default
  pub fn run(n i64) i64 {        // a default
    step(n) * scale + offset;
  }
}

A default's body is a template, as a generic's is: it is type-checked in each module that conforms, where the requirements it calls are known, and not where the trait is written. So a mistake in a default is reported at the trait's line, as instantiated by the first module that conforms; a framework package that no module of its own conforms to does not see it. Give such a package a small conforming module in its tests.

A module trait is a declaration of the module whose file writes it, like a type: it is imported, folded and made public as any declaration is. A module says it conforms on its mod line, with is:

mod plain is Runner;

import hosts use Runner;

pub fn step(n i64) i64 {
  n + 1i64;
}

pub var @threadlocal scale = 2i64;

Now plain.run(4i64) is 110: plain's own step, times its scale, plus the default offset.

  • Conformance is declared, never inferred. A module whose declarations happen to match a trait does not conform to it unless its mod line says so. A module conforms to one trait, named by one name that the module can reach; a trait further away is imported and folded in first.
  • It is checked where it is written. For each requirement the module must declare a function whose signature is the trait's or a subtype of it, by a recast alone, as a method meets a trait's (Variance): a &P for the trait's &mut P parameter, a reference to a variant for one to its enum, never a wider number, a value made an Option, or a variant held by value for its enum. Or it declares a global of the trait's type and permission. A requirement the module does not declare, or declares differently, is an error at the is, naming the member.
  • Defaults are copied into the module. A default the module does not declare becomes the module's own: plain.run and plain.offset are functions and globals of plain, reached like any other. Where a default's body names another member of the trait, the copy names the module's, so plain.run calls plain.step. A default global is storage of each module that takes it.
  • A module may override a default by declaring its own, with the same shape: the copies of the other defaults then use it.
  • Conformance is static. A module is one instance, so nothing is dispatched while the program runs, and there is no virtual reference to a module. A member of the trait has no code or storage of its own, so Runner.run is an error: name it through a module that conforms.
  • A module trait holds functions and globals only. A trait requiring a type is not implemented, and neither is a generic function or an overload name among its members.

On the mod line, is comes after extends and before a default fold: mod layered extends plain is Runner use run;. A module that extends a conforming module takes the defaults that module took as that module's, as it takes all its names.

Calls that may reshape a borrowed collection#

One rule matters only when a collection is borrowed across a call through a reference that may change what it reaches. The type behind a virtual reference is hidden, so a call through a mutable one (&<mut Trait, or a &<mut fn called) is assumed able to reshape any collection: it may be a struct that holds the list, or a closure that captured it. A borrow of an element of a collection that another path reaches (a &mut parameter, a field of self) is refused across such a call (error 1339; 1425 for a reference narrowed into an enum's variant). The cures are to lend the collection &imm, which nothing can write, to mark the reference + ShapeKeeping, or to copy what is needed out before the call. A read-only virtual reference (&<Trait, &<fn) cannot write what it reaches and is not affected. The rule itself, for every call, is in Elements of a collection and changing its shape, and the same rule seen from a closure, with a visitor example, in Calling a closure while a collection is borrowed.

You can now write code against an interface, and choose whether the part behind it is settled while the program is compiled or while it runs. The same idea at the function's layer, behaviour handed around as a part, is next.