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!
25self 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 5a + 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:
initanddeinitare rejected at resolution: construction belongs to the type itself.static/conststorage members (static val / var / prop, const) are categorically rejected (static storage cannot attach to the extended type); the static surface only admitsstatic funcandstatic event.eventandevent propinjected through an extension work across the whole chain: declaration, emit,~>subscription, write-position broadcast, and pipeline segment firing all as usual.bindis 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> instantiationA 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());extMembers 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.
