Skip to content

RFC-011a: Interface Implementation and Dynamic Dispatch ​

Parent RFC: RFC-011: generics System Design

This RFC supplements and replaces the interface constraint portion of RFC-011 §2.1-2.4.

Summary ​

RFC-011 defines the generics system but does not detail the interface implementation mechanism. This document supplements:

  1. Interface declaration: Interfaces are parameterized types—(Self: Type) -> Type, with concrete types passed in at implementation time
  2. Method implementation: Both internal and external declarations are supported
  3. Overloading rules: Different signatures permit overloading; identical signatures report an error (overriding prohibited)
  4. Default values: Write = value directly after the field
  5. Dynamic dispatch: compile-time type collection + interface matching, no vtable

Core design:

yaoxiang
# Interface definition (parameterized type, Self is an explicit type parameter)
Animal: (Self: Type) -> Type = {
    speak: (self: &Self) -> String,
}

# Type definition (internal declaration)
Dog: Type = {
    x: Int = 10,
    Animal(Dog),  # Interface instantiation, Self ↦ Dog
    speak: (self: &Dog) -> String = "Woof",
}

# External declaration (overload)
Dog.speak: (self: &Dog, volume: Int) -> String = "WOOF"

# Heterogeneous container (dynamic dispatch)
animals: List(Animal) = [Dog.new(), Cat.new()]
animals[0].speak()  # "Woof"

Receiver spelling convention (coordinating with RFC-009 ownership semantics):

  • Method receivers follow signature semantics: &Self = borrow (interface default convention—method calls do not consume the receiver), &mut Self = mutable borrow, value-type Self = consume the receiver (Move, RFC-009).
  • The Self in the impl-side signature is an alias for the impl type: interface speak: (self: &Self) matches both impl (self: &Dog) and (self: &Self) (after Self↦impl type substitution, fully consistent, §3).
  • The value-receiver spelling in historical examples ((self: Self)) was meant to indicate borrow; this document has uniformly migrated to explicit &Self. The value spelling now exclusively retains "consume" semantics and is no longer mixed.

Eliminated complexity:

  • ❌ No impl keyword
  • ❌ No Self magic keyword (Self is an explicit type parameter, no different from T)
  • ❌ No dyn Trait + 'a annotation
  • ❌ No vtable (compile-time type collection + enum wrapping)
  • ❌ No overriding (unified overloading rules)

Motivation ​

Insufficiencies of RFC-011 ​

RFC-011 defines the generics system but does not detail:

ProblemDescription
Interface declaration syntaxHow to declare that a type implements an interface?
Method implementation locationInternal or external declaration?
Overloading rulesHow are same-named methods handled?
Default value syntaxHow to set default values for fields?
Dynamic dispatchHow are heterogeneous containers implemented?

Design goals ​

  1. Concise: No impl keyword needed
  2. Flexible: Both internal and external method implementations are supported
  3. Unified: Consistent overloading rules
  4. Convenient: Concise default value syntax
  5. Zero overhead: No vtable, compile-time type collection

Comparison with Rust ​

FeatureRustYaoXiang
Interface declarationimpl Animal for Dog { ... }Dog: Type = { Animal(Dog), ... }
Method implementationIn impl blocksInternal or external
OverloadingNot supportedSupported (different signatures)
Default valuesRequires #[default]Write = value directly
Heterogeneous containersVec<Box<dyn Animal + 'a>>List(Animal)
Dynamic dispatchVtable lookupcompile-time type collection
Self keywordMagic keyword, implicit quantificationExplicit type parameter, equal to T

Proposal ​

1. Interface declaration ​

Core rule: An interface is a parameterized type (Self: Type) -> Type. Self is an explicit type parameter, not a magic keyword. Pass in the concrete type at implementation time.

yaoxiang
# Interface definition (fully consistent with RFC-011 generic types)
Animal: (Self: Type) -> Type = {
    speak: (self: &Self) -> String,
}

# Type declaration implements the interface
Dog: Type = {
    x: Int,
    Animal(Dog),  # Instantiate interface, Self ↦ Dog
}

Compiler processing:

  1. Recognize Animal(Dog) as an instantiation call of (Self: Type) -> Type
  2. Perform Self ↦ Dog substitution: expand Animal(Dog) → { speak: (self: &Dog) -> String }
  3. Check whether Dog provides all required methods (signature matching)
  4. If passed → generate implementation proof
  5. If failed → compilation error

Expansion equivalence:

yaoxiang
Dog: Type = {
    x: Int,
    Animal(Dog),  # Expand to Animal's methods, preserve source marker
}

# Equivalent to (preserving source information)
Dog: Type = {
    x: Int,
    speak: (self: &Dog) -> String,  # From Animal, Self replaced by Dog
}

Why source markers are needed:

  • Direct expansion loses source information
  • Source markers are used to generate implementation proofs
  • The runtime uses the proof to locate the correct method

1.1 Self type parameter and type checking timing ​

Self is the interface's explicit type parameter, not a magic keyword. Animal: (Self: Type) -> Type and List: (T: Type) -> Type are the same thing—(Type) -> Type type constructors.

Type checking timing:

  • At interface definition: The Self in { speak: (self: &Self) -> String } is an abstract type parameter, with only syntactic checking performed.
  • At instantiation point: When Animal(Dog) is invoked, perform Self ↦ Dog and conduct full type checking after expansion (signature matching, method existence).

This avoids the problem in RFC-011 where Self is an implicit magic keyword—Self does not appear in type definitions; it only appears once in the interface parameter list, fully equal to T.

1.2 Field name and method name namespace ​

The type's field names and method names share the same namespace. After interface expansion, if an interface method name conflicts with a type field name, the compiler reports an error:

yaoxiang
Drawable: (Self: Type) -> Type = {
    x: (self: &Self) -> Int,    // Method named x
}

Point: Type = {
    x: Int,                     // Field also named x
    Drawable(Point),            // ❌ Compilation error: Drawable requires method x, conflicting with field x
}

Field access point.x and method call point.x() are syntactically indistinguishable. A unified namespace avoids ambiguity.

2. Method implementation ​

Core rule: Both internal and external method implementation declarations are supported.

2.1 Internal declaration ​

yaoxiang
Dog: Type = {
    x: Int = 10,
    Animal(Dog),
    speak: (self: &Dog) -> String = "Woof",  # Method implementation internal
}

2.2 External declaration ​

yaoxiang
Dog: Type = {
    x: Int,
    Animal(Dog),
}

# Method implementation external
Dog.speak: (self: &Dog) -> String = "Woof"

2.3 Mixed declaration ​

yaoxiang
Dog: Type = {
    x: Int = 10,
    Animal(Dog),
    speak: (self: &Dog) -> String = "Woof",  # Some methods internal
}

# Some methods external
Dog.play: (self: &Dog) -> Void = { ... }

Compiler processing:

  1. Collect all definitions (internal and external)
  2. Group by signature (overload)
  3. Check for overriding (report error)
  4. Check interface completeness
  5. Generate implementation proof

3. Overloading and overriding ​

Core rule:

  • Different signatures → overloading → allowed
  • Identical signatures → overriding → report error

3.1 Overloading (allowed) ​

yaoxiang
# Different parameter types, overloading allowed
Dog.speak: (self: &Dog) -> String = "Woof"
Dog.speak: (self: &Dog, volume: Int) -> String = "WOOF"

3.2 Overriding (prohibited) ​

yaoxiang
# Identical signatures, overriding prohibited
Dog.speak: (self: &Dog) -> String = "Woof"
Dog.speak: (self: &Dog) -> String = "Bark"  # ❌ Error: overriding not allowed

Error message:

Error: Dog.speak(self: &Dog) -> String duplicate definition
  --> file2:5:1
  |
5 | Dog.speak: (self: &Dog) -> String = "Bark"
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ duplicate definition
  |
  --> file1:3:1
  |
3 | Dog.speak: (self: &Dog) -> String = "Woof"
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ first definition

3.3 Unified rules ​

Internal and external declarations follow the same overloading/overriding rules:

yaoxiang
# Internal declaration
Dog: Type = {
    x: Int,
    Animal(Dog),
    speak: (self: &Dog) -> String = "Woof",
}

# External declaration (overload, allowed)
Dog.speak: (self: &Dog, volume: Int) -> String = "WOOF"

# External declaration (overriding, prohibited)
Dog.speak: (self: &Dog) -> String = "Bark"  # ❌ Error

4. Default values ​

Core rule: Write = value directly after the field, eliminating the need for constructors.

yaoxiang
Dog: Type = {
    x: Int = 10,  # Default value
    y: Int = 20,  # Default value
    Animal(Dog),
}

Compiler-generated constructors:

yaoxiang
# All fields have default values → generate no-arg constructor
Dog.new: () -> Dog = { x: 10, y: 20 }

# Some fields have default values → generate partial-arg constructors
Dog.new: (x: Int) -> Dog = { x: x, y: 20 }
Dog.new: (y: Int) -> Dog = { x: 10, y: y }

# Full-arg constructor
Dog.new: (x: Int, y: Int) -> Dog = { x: x, y: y }

External declaration of default values:

yaoxiang
Dog: Type = {
    x: Int,
    y: Int,
    Animal(Dog),
}

# External declaration of default values
Dog.x: Int = 10
Dog.y: Int = 20

Equivalent to internal declaration.

5. Compiler implementation ​

5.1 Interface descriptor ​

rust
// Compiler internal: interface descriptor
struct InterfaceDescriptor {
    name: String,
    self_param: TypeParam,     // Self type parameter
    methods: Vec<MethodSignature>,
}

5.2 Type definition ​

rust
// Compiler internal: type definition
struct TypeDefinition {
    name: String,
    fields: Vec<Field>,
    interface_instantiations: Vec<InterfaceInstantiation>,
}

// Interface instantiation (Self ↦ ConcreteType)
struct InterfaceInstantiation {
    interface: InterfaceId,
    self_type: TypeId,          // Concrete type that Self is replaced with
    methods: HashMap<MethodId, FunctionBody>,
}

5.3 Implementation proof ​

rust
// Compiler internal: implementation proof
struct ImplementationProof {
    type_id: TypeId,
    interface_id: InterfaceId,
    methods: Vec<MethodPointer>,
}

5.4 Compilation flow ​

1. Parse type definitions, collect interface instantiation declarations (Animal(Dog))
2. For each interface instantiation, perform Self ↦ ConcreteType substitution
3. Expand interface method signatures, check signature matching
4. Collect all method definitions (internal and external)
5. Group by signature (overload)
6. Check overriding (report error)
7. Check interface completeness
8. Generate implementation proof

6. Dynamic dispatch ​

Core design: compile-time type collection + interface matching, no vtable.

6.1 Heterogeneous containers ​

Animal is (Self: Type) -> Type. List(Animal) uses the uninstantiated interface type constructor as an existential type: ∃S. Animal(S)—"there exists some type S such that S implements Animal(S)".

yaoxiang
# Interface definition
Animal: (Self: Type) -> Type = {
    speak: (self: &Self) -> String,
}

# Type definition
Dog: Type = {
    x: Int,
    Animal(Dog),
    speak: (self: &Dog) -> String = "Woof",
}

Cat: Type = {
    y: Int,
    Animal(Cat),
    speak: (self: &Cat) -> String = "Meow",
}

# Heterogeneous container — Animal uninstantiated = existential type
animals: List(Animal) = [Dog.new(), Cat.new()]
animals[0].speak()  # "Woof"
animals[1].speak()  # "Meow"

Ownership semantics: Insertion into a heterogeneous container is Move semantics (RFC-009). Dog.new() is moved into the AnimalGroup::Dog enum variant, and the original variable is no longer available.

yaoxiang
dog = Dog.new()
animals: List(Animal) = [dog]
# dog.speak()  ← ❌ Compilation error: dog has been moved

6.2 compile-time type collection ​

Core strategy: ownership tracking, incremental construction. Not scanning the compile-time all types that implement the interface—rather, incrementally collecting at each List(Animal)ownership operation point:

yaoxiang
// Construction point
animals: List(Animal) = [Dog.new()]       // AnimalGroup = { Dog(Dog) }

// append point
animals.append(Cat.new())                  // Compiler sees Cat at append → expands to { Dog, Cat }
animals.append(Bird.new())                 // Expands further to { Dog, Cat, Bird }

Compiler processing (incremental):

  1. Encounter List(Animal) constructed for the first time → generate initial enum (all known constructor types in the current compilation unit)
  2. Each append / push / index assignment → check whether the value type is already in the enum; if not, expand the enum variant
  3. Generate monomorphized match dispatch code for the final enum
  4. Cross-compilation-unit: rely on LTO (Link-Time Optimization) to merge enum variants. When Animal as an existential type is passed across compilation unit boundaries, each unit generates partial enum variants, which are merged into a complete enum at the linking stage.

Auto-generated enum:

yaoxiang
# Compiler auto-generated (invisible to user)
AnimalGroup: Type = {
    Dog(Dog),
    Cat(Cat),
    Bird(Bird),    # ← append(Bird.new()) triggers incremental expansion
}

# List(Animal) internally equivalent to List(AnimalGroup)

6.3 Interface matching check ​

Key insight: Interface matching is a compile-time check, even if the type comes from a dynamically loaded plugin.

yaoxiang
# Plugin system
plugin = load_plugin("bird.so")

# Compiler checks: plugin.create_bird() return type must implement Animal
bird: Animal = plugin.create_bird()  # compile-time check, existential type

# Put into heterogeneous container — append point triggers enum expansion
animals: List(Animal) = [Dog.new(), Cat.new()]
animals.append(bird)                 # Compiler: (1) verify bird implements Animal (2) expand enum

Compiler processing:

  1. Check the return type of the append argument
  2. Verify whether the type implements the target interface
  3. If passed → expand enum, allow insertion
  4. If failed → compilation error

6.4 Runtime dispatch ​

Call flow (compile-time enum match, ImplementationProof erased):

animals[0].speak()
  ↓
Compiler-generated match:
  match animals[0] {
    AnimalGroup.Dog(d) => d.speak(),
    AnimalGroup.Cat(c) => c.speak(),
    AnimalGroup.Bird(b) => b.speak(),
  }

Brand projection (interaction with RFC-009a): The match pattern binding AnimalGroup.Dog(d) produces a #animals[0].Dog sub-brand in the brand tree, equivalent to field projection (#42.field_x). The ReadToken(d) brand chain created by d.speak() is animals → animals[0] → d → ReadToken(d), which the borrow checker validates via brand tree prefix matching for conflicts.

Subscript access type: animals[0] returns &AnimalGroup (compiler-generated enum type); the user cannot directly obtain &mut Animal. Mutable access is achieved indirectly through interface methods (e.g., animals[0].mutate() internally expands to AnimalGroup::Dog(d) => d.mutate()).

Comparison with vtable:

Vtable (Rust)compile-time Enum (YaoXiang)
Lookup methodVtable pointer → method pointerEnum match → direct call
Runtime overheadOne indirect addressingbranch (can be optimized by CPU branch prediction)
compile-time generationVtableEnum + match
User annotationRequires dyn Trait + 'aNot required
ImplementationProofN/AErased at compile-time, nonexistent at runtime

YaoXiang's advantages:

  • No brand annotation required
  • compile-time type safety
  • User-transparent (no need to write dyn Animal)
  • ImplementationProof is a pure compile-time concept with zero runtime overhead

6.5 Limitations and scope ​

Within the current period (single compilation unit): Full support. Ownership tracking covers all append/construction points, with incremental enum construction.

Cross-compilation-unit: Rely on LTO (Link-Time Optimization) to merge enum variants. Animal is passed across compilation unit boundaries as an existential type (∃S. Animal(S)). Each unit generates partial enum variants, which are merged at the linking stage.

Not supported: Runtime dynamic types (full duck typing). The type set is fully known at compile time.

6.6 Implementation notes (Phase 3, landed in v1) ​

The semantics of §6 (heterogeneous containers, compile-time membership check, dispatch by actual type, type set closed at compile time) have all landed. The implementation form has been concretized in the mechanism layer as follows:

  • Type collection: The implementation type set is collected in one go for the entire compilation unit according to ImplementationProof, replacing §6.2's "incremental collection at each ownership operation point". The semantics are equivalent within a single compilation unit (extra dead variants are harmless); the value of incremental collection is in cross-unit scenarios, attributed to v2 (see below).
  • Representation: The compiler synthesizes Animal$Group variant types, as pure IR/bytecode/runtime artifacts (instructions CreateVariant/VariantTag/VariantPayload, runtime value RuntimeValue::Enum), with MonoType unaware—the typecheck layer's user-visible type is still the interface name. Each concrete value entering an existential type position is automatically wrapped as a variant value (unified opaque representation, §6.4 semantics).
  • Wrapping point: typecheck performs targeted walks at positions of "concrete vs existential" determination (annotated let/call argument/return/list literal element), producing span-keyed enforcement tables; IR generation injects wrapping by span. Missed wrapping is loudly rejected by runtime guards (VariantTag/VariantPayload validates that the value must be a named group variant value), with the worst case being explicit runtime errors during testing—never silently producing incorrect data.
  • Dispatch: Variant-number comparison jump chains; each arm unpacks the payload and then statically calls the concrete method. RFC-004 rebinding form (Type.method = fn[n]) participates in dispatch in the same way after rearrangement by binding position.
  • Isolation: Legacy trait constraints (Drawable: Type = {..} style, no generics parameters) do not go through variant dispatch; behavior is unchanged.

v1 boundary (subsequent phases): Cross-unit LTO variant merging (§6.5); pattern matching on Group values (depends on IR support for variant patterns in match); reflection interaction; Move-into-container semantics; Any/type variable transit flow and inferred-typed lambda boundaries (fallback = runtime guard).


Use case analysis ​

Basic interface implementation ​

yaoxiang
# Interface definition
Animal: (Self: Type) -> Type = {
    speak: (self: &Self) -> String,
}

# Type definition
Dog: Type = {
    x: Int = 10,
    Animal(Dog),
    speak: (self: &Dog) -> String = "Woof",
}

# Usage
dog = Dog.new()
dog.speak()  # "Woof"

Multiple interface implementations ​

yaoxiang
# Multiple interfaces
Animal: (Self: Type) -> Type = {
    speak: (self: &Self) -> String,
}

Pet: (Self: Type) -> Type = {
    name: (self: &Self) -> String,
}

# Type implements multiple interfaces
Dog: Type = {
    x: Int = 10,
    Animal(Dog),
    Pet(Dog),
    speak: (self: &Dog) -> String = "Woof",
    name: (self: &Dog) -> String = "Buddy",
}

# Usage
dog = Dog.new()
dog.speak()  # "Woof"
dog.name()   # "Buddy"

Generic interfaces ​

yaoxiang
# Generic interface
Container: (Self: Type, T: Type) -> Type = {
    add: (self: &mut Self, item: T) -> Void,
    get: (self: &Self, index: Int) -> T,
}

# Implement generic interface
IntList: Type = {
    data: Array(Int),
    Container(IntList, Int),
    add: (self: &mut IntList, item: Int) -> Void = ...,
    get: (self: &IntList, index: Int) -> Int = ...,
}

Heterogeneous containers ​

yaoxiang
# Interface definition
Animal: (Self: Type) -> Type = {
    speak: (self: &Self) -> String,
}

# Type definition
Dog: Type = {
    x: Int,
    Animal(Dog),
    speak: (self: &Dog) -> String = "Woof",
}

Cat: Type = {
    y: Int,
    Animal(Cat),
    speak: (self: &Cat) -> String = "Meow",
}

# Heterogeneous container
animals: List(Animal) = [Dog.new(), Cat.new()]

# Usage
for animal in animals {
    print(animal.speak())
}
# Output:
# Woof
# Meow

Plugin system ​

yaoxiang
# Interface definition
Plugin: (Self: Type) -> Type = {
    name: (self: &Self) -> String,
    execute: (self: &Self) -> Void,
}

# Main program
main: () -> Void = {
    # Load plugins
    plugin1 = load_plugin("plugin1.so")
    plugin2 = load_plugin("plugin2.so")

    # Compiler checks: plugin1 and plugin2 must implement Plugin interface
    plugins: List(Plugin) = [plugin1, plugin2]

    # Execute all plugins
    for plugin in plugins {
        print(plugin.name())
        plugin.execute()
    }
}

Trade-offs ​

Advantages ​

  1. Concise: No impl keyword needed
  2. Flexible: Both internal and external method implementations supported
  3. Unified: Consistent overloading rules
  4. Convenient: Concise default value syntax
  5. Zero overhead: No vtable, compile-time type collection
  6. Type safe: Interface matching is a compile-time check
  7. User-transparent: No need to write dyn Animal + 'a

Disadvantages ​

  1. Limitation: No runtime dynamic types (full duck typing) supported
  2. compile-time overhead: Need to generate enum variants and match dispatch code for each interface
  3. Type set: Must be fully known at compile time (within a single compilation unit)

Mitigations ​

  1. Plugin system: Supported via compile-time interface matching check
  2. Type set: Ownership tracking, incremental construction—collected at each append/construction point, not a global scan
  3. Cross-compilation-unit: Link-time merging of enum variant sets, sharing mechanisms with link-time monomorphization

Alternatives ​

PlanWhy not chosen
impl keywordIncreases syntactic complexity
Vtable (dyn Trait)Requires brand annotation ('a)
Full duck typingRuntime overhead, type unsafe
Enum wrapping (manual)Heavy user burden

Relationship with RFC-009 ​

Brand and interface implementation:

  • Interface implementation is at the type layer, not involving brand
  • Brand is at the borrow proof layer (RFC-009a)
  • The two are orthogonal, mutually unaffected

Dynamic dispatch and brand:

  • Dynamic dispatch uses implementation proof, no brand annotation needed
  • Implementation proof is generated at compile time, zero runtime lookup
  • Avoids the complexity of dyn Trait + 'a

Ownership of heterogeneous containers:

  • Putting into List(Animal) is Move semantics (RFC-009); the original variable cannot be accessed again
  • Subscript access animals[0] returns &AnimalGroup (compiler-generated enum), the brand projection chain is animals → animals[0] → enum_variant → field
  • Mutable access is achieved indirectly through interface methods, not exposing &mut AnimalGroup to the user

Interface inheritance ​

Interfaces can include other interfaces. No new syntax introduced—uses exactly the same syntax position as type declaration of interfaces:

yaoxiang
Animal: (Self: Type) -> Type = {
    speak: (self: &Self) -> String,
}

Pet: (Self: Type) -> Type = {
    Animal(Self),                       # Pet inherits Animal — no new keyword
    name: (self: &Self) -> String,
}

# When Dog implements Pet, it must satisfy all methods of both Animal and Pet
Dog: Type = {
    x: Int,
    Pet(Dog),
    speak: (self: &Dog) -> String = "Woof",  # From Animal
    name: (self: &Dog) -> String = "Buddy",  # From Pet
}

Design principle: Inheritance exists but is not encouraged to overuse. The main composition approach is through multiple interface instantiations (Dog: Type = { Animal(Dog), Pet(Dog), ... }). A type can directly declare all interfaces it satisfies, without needing an inheritance tree to express this. Interface inheritance is used only when there is a clear "is-a" hierarchy.

Compiler processing: Expand the inheritance chain. Pet(Self) expands to { all methods of Animal(Self), name: ... }. When Dog declares Pet(Dog), Self ↦ Dog, and the compiler verifies that Dog satisfies all methods of both Animal(Dog) and Pet(Dog).

Self substitution in interface inheritance: In Pet: (Self: Type) -> Type = { Animal(Self), ... }, the Self in Animal(Self) is the Self parameter of Pet—it will be lazily substituted. When Dog implements Pet(Dog), Self ↦ Dog, and Animal(Self) becomes Animal(Dog). This is fully consistent with the parameter passing semantics of generic functions.

Default method implementations ​

Interfaces can provide default implementations for methods. The implementing type can choose to override or inherit the default implementation:

yaoxiang
fmt: (Self: Type) -> Type = {
    display: (self: &Self) -> String,                      # Must implement
    debug: (self: &Self) -> String = self.display(),       # ✅ References same-interface method
    summary: (self: &Self) -> String = f"<{self.name}>",  # ❌ Compilation error: self.name not in fmt
}

Core constraint: interfaces cannot assume supertype implementation. Default methods can only reference methods already declared in the same interface. The concrete type's fields or other interfaces' methods are invisible to default methods—an interface is a closed contract, and cannot reach into the implementing type's pocket. Violating this constraint reports an error directly at interface definition time.

Inheritance can assume subtype implementation: When interface Pet(Self) inherits Animal(Self), default methods of Pet can use methods declared in Animal—because of inheritance, they are guaranteed to exist.

yaoxiang
Animal: (Self: Type) -> Type = {
    speak: (self: &Self) -> String,
}

Pet: (Self: Type) -> Type = {
    Animal(Self),                                              # Inheritance
    name: (self: &Self) -> String,
    introduce: (self: &Self) -> String = self.name() + " says " + self.speak(),  # ✅ speak comes from inherited Animal
}

compile-time behavior: When a type implements an interface, for each method:

  1. Type provides → use the type's method
  2. Type does not provide, interface has default → compiler inlines default implementation onto the type (zero vtable overhead)
  3. Type does not provide, interface has no default → compilation error

Design principle: Default methods are similar to the auto-derive mechanism of Copy/Clone—the compiler auto-generates when needed, and the user can override. No virtual/override/super keywords are introduced. ​

Implementation phases ​

PhaseContentDepends on
Phase 1Interface declaration syntax ((Self: Type) -> Type) + Self type parameterRFC-011
Phase 2Interface instantiation (Animal(Dog)) + Self ↦ ConcreteType substitutionPhase 1
Phase 3Internal/external declaration of method implementationPhase 2
Phase 4Overloading and overriding rulesPhase 3
Phase 5Default value syntaxPhase 3
Phase 6Interface inheritancePhase 4
Phase 7Default method implementationsPhase 6
Phase 8Implementation proof generationPhase 7
Phase 9compile-time type collectionPhase 8
Phase 10Dynamic dispatch implementationPhase 9

Design decision record ​

DecisionDecisionReasonDate
Interface declaration syntaxInterfaces are parameterized types (Self: Type) -> Type, instantiated at implementationEliminate Self magic keyword, fully consistent with the RFC-011 generics system2026-06-14
Self type parameterExplicit type parameter; only syntactic check at interface definition, full check at instantiation pointAvoid free type variables in HM inference2026-06-14
Dynamic dispatchcompile-time type collection + auto-generated enumNo vtable, zero runtime lookup, user-transparent2026-06-14
External method declarationSupportedFlexibility equivalent to internal declaration; compiler responsible for cross-file collection2026-06-14
OverridingProhibited (same signature reports error)Overriding causes unpredictable behavior; overloading covers all cases2026-06-14
Interface inheritanceSupported, no new syntaxSame syntax position as type declaration of interfaces. Encourages composition (multi-interface instantiation); discourages deep inheritance trees2026-07-03
Default method implementationsSupported, similar to Copy/Clone auto-deriveInterface provides default body; compiler inlines onto implementing type; user can override. No virtual/override introduced2026-07-03
Default method constraintsVerify at interface definition: can only reference same-interface methods, cannot assume supertype implementationInterface is a closed contract. Inheritance can assume subtype implementation, but interfaces cannot assume the implementing type's fields/methods2026-07-03
Type collection strategyOwnership tracking, incremental construction—collected at each append/construction pointNot a global scan of all implementers, but incremental enum expansion by ownership operation point2026-07-03
ImplementationProofPure compile-time concept, erased at runtimeruntime takes enum match dispatch; proof only used for compile-time verification2026-07-03
Cross-compilation-unitLTO merges enum variantsExistential types passed across compilation unit boundaries; each unit generates partial enum; LTO stage merges2026-07-03
Field/method namespaceUnified namespace, conflict reports errorField access point.x and method call point.x() are syntactically indistinguishable; unification avoids ambiguity2026-07-03
Heterogeneous container ownershipMove semantics; original variable unusable after insertionConsistent with RFC-009 ownership model2026-07-03
Brand projectionmatch pattern binding produces sub-brand, equivalent to field projectionConsistent with RFC-009a brand tree mechanism; enum variant projection is a valid path in the brand tree2026-07-03
Receiver spelling convention&Self borrow / &mut Self mutable borrow / value = MoveReceivers follow signature semantics (RFC-009); interface default is borrow; historical value spelling migrated to &Self2026-08-30

Open questions ​

  • [x] Interface inheritance (interfaces can inherit other interfaces) → Supported, no new syntax. Pet: (Self: Type) -> Type = { Animal(Self), ... }
  • [x] Default method implementations (interfaces can provide default implementations) → Supported, similar to Copy auto-derive. Interface provides body; compiler inlines on demand
  • [x] Self as implicit magic keyword → Eliminated. Self is an explicit type parameter; the interface is (Self: Type) -> Type
  • [ ] Advanced uses of interface constraints (associated types, GAT)—associated types implemented via generic interface parameters (Container: (Self: Type, T: Type) -> Type); GAT requires further design
  • [ ] Interaction with closures (closures implementing interfaces)—initial strategy: closures do not support directly implementing interfaces; a wrapper type is required. Interface implementations of anonymous types deferred to subsequent RFCs

References ​


Lifecycle and destination ​

StatusLocationDescription
Accepteddocs/design/rfc/accepted/Formal design document