Skip to Content

Extension

extension injects new methods into an existing type: the type’s source stays untouched, and completion and calls on the method surface behave like native members. Both scalars and user types can be extended.

Scalar Receivers

extension string { public func Shout() -> string => self.ToUpper() + "!"; } extension int { public func Squared() -> int => self * self; } print("hmph".Shout()); val n = 5; print(n.Squared());
HMPH! 25

self is the receiver itself. Calling directly on a literal (7.Squared()) or on a variable both work.

User-Type Extensions

struct Point { public var x: int = 0; public var y: int = 0; } extension Point { public func Manhattan() -> int { return (x < 0 ? -x : x) + (y < 0 ? -y : y); } public func Show() -> string => $"({x}, {y})"; } val p = new Point() { x: 3, y: -4 }; print(p.Manhattan(), p.Show());
7 (3, -4)

The extension body accesses the target type’s fields directly (including the same-module private field surface). Point itself doesn’t change by a single line.

What an Extension Body Can Declare

What an extension body can declare is the “injection surface”: func, operator overloads, props, static func and static event, event and event prop (including before / after / finally pipeline segments, wired to fire on injection), plus the impl clause. Once injected through an extension, operators and props are members of the target type:

struct Pt { public var x: int = 0; public var y: int = 0; } extension Pt { public operator +(self, o: Pt) -> Pt => new Pt() { x: x + o.x, y: y + o.y }; public prop Norm: int => x * x + y * y; } val a = new Pt() { x: 1, y: 2 }; val b = new Pt() { x: 10, y: 20 }; val s = a + b; print(s.x, s.y, a.Norm);
11 22 5

a + b goes through the extension-provided operator +, and a.Norm through the extension prop; at the call site they are no different from native members. The boundary in the other direction is just as clear:

  • init and deinit are rejected at resolution: construction belongs to the type itself.
  • static / const storage members (static val / var / prop, const) are categorically rejected (static storage cannot attach to the extended type); the static surface only admits static func and static event.
  • event and event prop injected through an extension work across the whole chain: declaration, emit, ~> subscription, write-position broadcast, and pipeline segment firing all as usual.
  • bind is not accepted in an extension body; binding declarations stay in the type body.

Activation Rules

Extension members are activated automatically by importing the module that defines them: whichever module they are defined in, import that module and they are available.

Generic Targets

extension Crate<T> // injects the generic declaration itself extension Crate<int> // specialization: injects only the Crate<int> instantiation

A bare identifier is always the type parameter; to specialize to a particular instantiation, spell out Crate<int> in full.

When a specialized version and the generic version coexist with the same signature, the specialized one wins. Specializing to a user-named type requires the dot-separated prefix (Crate<Mod.Foo>). The target’s type arguments must be either all type parameters or all concrete types.

Shadowing and Overload Coexistence

A type’s own method shadows an extension method of the same name and signature; with different signatures they coexist as normal overloads:

class Slot<T> { public var item: T; public func Describe() -> string => $"own {item}"; } extension Slot { public func Describe() -> string => $"ext {item}"; public func DescribeLoud() -> string => $"ext {item}!"; } val s = new Slot<int>(); s.item = 3; print(s.Describe()); print(s.DescribeLoud());
own 3 ext 3!

Describe reaches the type’s own version, and the extension version is shadowed. DescribeLoud is provided only by the extension and is directly available.

Extensions and Protocols

extension T impl P propagates protocol satisfaction straight to the target type: the extension body provides the members P requires, and T is judged as satisfying at both protocol positions and generic constraint positions — T itself writes no impl:

protocol Pinger { func Ping(self) -> string; } extension Holder impl Pinger { public func Ping(self) -> string => "ext"; } class Holder { } val p: Pinger = new Holder(); print(p.Ping());
ext

Members injected by an extension can also satisfy the type’s own declared impl P requirement. The other way around, extension members attached to a protocol do not become new requirements of the protocol; a protocol’s requirement set only looks at the protocol declaration chain. A protocol’s static members are also conventionally implemented via extensions; see the Lerpable example on the Protocol page.

Scalar targets have one hard boundary: members of extension int impl P work on direct calls, but scalars cannot enter protocol positions (val p: P = 5; reports MS3101).

Notes

  • The type’s own methods shadow extension methods of the same name and signature; same name with a different signature is a normal overload.
  • Built-in containers (list / set / map / array) have a closed method surface and cannot be extension targets (MS4008).
  • Extensions cannot add fields or change a type’s storage layout.
Last updated on October 11, 2026