Skip to content

Extensions

extend adds methods to a type after its declaration, or adds an explicit trait implementation for an existing type. Use extensions to keep a type’s core definition separate from optional behavior.

struct User {
public Int id;
}
extend User {
public Int id_value(self) {
self.id
}
}
Int value = user.id_value();

Extension members follow ordinary member visibility. Write public on methods that other modules should call.

extend does not currently support init or deinit.

extend Type: Trait { ... } adds a trait implementation:

trait HasValue {
Int value(self);
}
struct User {
public Int id;
}
extend User: HasValue {
public Int value(self) {
self.id
}
}

A type does not satisfy a trait just because it happens to have methods with matching signatures. The implementation must be declared on the type or in extend Type: Trait.

Extensions can be generic:

struct Holder<T> {
public T value;
}
extend <T> Holder<T> {
public T get(self) {
self.value
}
}

Use @where to make an extension available only for selected type arguments:

@where(T == Int)
extend <T> Holder<T> {
public Int only_int() {
7
}
}
Int value = Holder<Int>.only_int();

@where can match concrete type shapes. _ is an anonymous placeholder, accepting any type argument in that position:

@where(T == _^)
extend <T> Holder<T> {
public Bool stores_box() {
true
}
}
@where(T != _?)
extend <T> Holder<T> {
public Bool stores_non_optional() {
true
}
}

Use : for trait bounds. Use == or != for concrete type-shape matching:

@where(T: Sequence<Element = UInt8>)
Int count_bytes<T>(T value);
@where(T == _^)
Int box_only<T>(T value);

The extension target itself can also use anonymous placeholders:

extend <T> T& {
public Bool is_reference() {
true
}
}
extend <T> T[]^ {
public Bool is_owned_slice() {
true
}
}

Compiler-owned type names are hidden. Extensions target the surface forms directly, including generic patterns such as extend <T> T& and extend <T> T[]^.

Extensions can be used across module boundaries, but they are not globally active. A file must import the module that defines the extension before extension members participate in lookup.

holder_ext.jiang
public struct Holder<T> {
public T value;
}
@where(T == Int)
public extend <T> Holder<T> {
public Int only_int() {
11
}
}
main.jiang
import ext = "holder_ext.jiang";
alias ImportedHolder = ext.Holder;
Int value = ImportedHolder<Int>.only_int();

Use public extend when an extension should be available to modules that import it. An extension without public is only visible inside the module that defines it.

Jiang does not impose a global orphan rule on extensions: a module may extend a public external or builtin type. Lookup only considers extensions visible from the use site. A concrete target is more specific than a generic target; if multiple callable extensions with the same specificity match, the call is ambiguous rather than being selected by declaration order.

public import can re-export public extensions from another module:

mid.jiang
public import leaf = "holder_ext.jiang";
app.jiang
import mid = "mid.jiang";
alias Holder = mid.leaf.Holder;
Int value = Holder<Int>.only_int();