Skip to Content

Mixin

mixin is a declarative block of reusable members that is cloned whole into the class hosting it at compile time. It solves the problem of “several unrelated classes need the same set of methods” without occupying the single inheritance slot.

The full declaration form is mixin Name<T> requires R1, R2 impl P1, P2 where T: Ord { }; requires, impl, and where combine as needed.

Declaration and Mounting

mixin Noisy { public func Noise() -> string => "boom"; public func Twice() -> string => self.Noise() + self.Noise(); } class SilentBoom with Noisy { public func Noise() -> string => "own"; } val s = new SilentBoom(); print(s.Noise(), s.Twice());
own ownown

class ... with Noisy clones the mixin’s members in. Inside the cloned body, self.Noise() goes through the host object’s method table: the host’s own same-name method shadows the mixin version, and Twice ends up using the host’s Noise too.

What Can Be Mixed In

What a mixin can declare: nearly every member kind a type body can declare. Beyond methods, fields, props, static members, conversion operators, and events (including pipeline segments) are cloned whole into the host:

mixin Metered { public var hits: int = 0; public val tag: string = "meter"; public prop Twice: int => hits * 2; public static var built: int = 0; public func Bump() -> int { hits += 1; return hits; } } class Counter2 with Metered { } val c = new Counter2(); print(c.Bump(), c.Bump(), c.Twice, c.tag); print(new Counter2().hits); print(Counter2.built);
1 2 4 meter 0 0
  • val / var fields are cloned into the host’s instance layout, one independent copy per instance: c.Bump() works on its own hits, and a new instance starts from 0.
  • Props carry over in full form (automatic properties, expression bodies, computed; a static prop gets its own storage per host), and bare-name reads, writes, and compound assignments to a prop inside mixin method bodies (Level = Level + 3) route automatically to the host’s accessors and backing slot.
  • Static members are cloned too, accessed through the host type name (Counter2.built); the mixin itself has no static namespace, and MixinName.member cannot be used.
  • After cloning, consts hang off the host type position: both HostName.CONST and instance-position reads work.
  • event / event prop come along into the host’s subscription surface: ~> subscription at the host position, emit dispatch inside mixin methods, and full-pipeline broadcast on event prop writes all work as usual; pipeline segments (before / after / finally) are cloned along and fire too, under the same rules as pipelines declared in a host body — multiple hosts each fire on their own without cross-talk;
  • init and deinit cannot appear in a mixin body: construction and destruction belong to the host’s own lifecycle.

Mixin Inheritance

A mixin can only inherit from another mixin (mixin A : B), and members along the inheritance chain are cloned along with it:

mixin Loud : Noisy { public func Louder() -> string => self.Noise().ToUpper(); } protocol GreeterX { func Greet() -> string; } class Boombox with Loud impl GreeterX { public func Greet() -> string => "boom"; } val b = new Boombox(); print(b.Greet(), b.Noise(), b.Twice(), b.Louder());
boom boom boomboom BOOM

with Loud brings in Loud together with the Noisy it inherits; the host ends up with four members and can still impl protocols and take part in inheritance as usual.

requires: Mounting Preconditions

protocol LogSource { func Log() -> string; } mixin Echo requires LogSource { public func Shout() -> string => self.Log() + "!"; } class Event2 impl LogSource with Echo { public func Log() -> string => "event"; } print(new Event2().Shout());
event!

requires declares the conditions a host must satisfy before it can with the mixin — three kinds of target: protocols, base classes, and other mixins. Mounting errors when a condition is missing; “what this set of methods assumes the host has” becomes a compile-time contract.

Mixins and Protocols

A mixin body can carry an impl clause, but it only validates the mixin body itself (member self is checked against the mixin type) and does not make the host satisfy the protocol automatically: to flow as a protocol position, the host must write its own impl and supply same-shape members itself.

To make an existing type satisfy a protocol without writing impl, use the extension form extension T impl P; see “Extensions and Protocols” on the Extension page.

Conflicts and Disambiguation

When two mixins bring same-name members, hiding hides one of them and rename renames one; the two clauses can appear in either order and combine:

mixin Pair2 { public func Kick() -> string => "kick"; public func Punch() -> string => "punch"; } mixin Extra { public func Kick() -> string => "double kick"; } class Fighter with Pair2, Extra rename { Kick => DoubleKick } { } class Guard with Pair2, Extra hiding { Kick } { } val f = new Fighter(); print(f.DoubleKick(), f.Punch()); val g = new Guard(); print(g.Kick(), g.Punch());
double kick punch kick punch

After rename both members exist (Kick and DoubleKick). hiding discards Extra.Kick entirely, leaving Kick as Pair2’s version only.

Generic Mixins

mixin Boxed<T> { public func Wrap(v: T) -> string => $"wrapped {v}"; } class Crate2 with Boxed<int> { } print(new Crate2().Wrap(9));
wrapped 9

Type arguments are given at mount time (with Boxed<int>); mixin type parameters can carry where constraints too:

import Math.*; mixin Shouter<T> where T: Numeric { public func Show(v: T) -> string => $"value {v}"; } class Box2 with Shouter<int> { } print(new Box2().Show(5));
value 5

Notes

  • Mixin members are compile-time clones; no mixin instance exists at runtime.
  • A mixin cannot be instantiated, cannot be used as a type, and has no static namespace.
  • When you need runtime polymorphism (flowing as a protocol position), use a protocol; when you need behavior reuse, use a mixin.
Last updated on October 11, 2026