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:
- Interface declaration: Interfaces are parameterized types—
(Self: Type) -> Type, with concrete types passed in at implementation time - Method implementation: Both internal and external declarations are supported
- Overloading rules: Different signatures permit overloading; identical signatures report an error (overriding prohibited)
- Default values: Write
= valuedirectly after the field - Dynamic dispatch: compile-time type collection + interface matching, no vtable
Core design:
# 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-typeSelf= consume the receiver (Move, RFC-009). - The
Selfin the impl-side signature is an alias for the impl type: interfacespeak: (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
implkeyword - ❌ No
Selfmagic keyword (Selfis an explicit type parameter, no different fromT) - ❌ No
dyn Trait + 'aannotation - ❌ 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:
| Problem | Description |
|---|---|
| Interface declaration syntax | How to declare that a type implements an interface? |
| Method implementation location | Internal or external declaration? |
| Overloading rules | How are same-named methods handled? |
| Default value syntax | How to set default values for fields? |
| Dynamic dispatch | How are heterogeneous containers implemented? |
Design goals
- Concise: No
implkeyword needed - Flexible: Both internal and external method implementations are supported
- Unified: Consistent overloading rules
- Convenient: Concise default value syntax
- Zero overhead: No vtable, compile-time type collection
Comparison with Rust
| Feature | Rust | YaoXiang |
|---|---|---|
| Interface declaration | impl Animal for Dog { ... } | Dog: Type = { Animal(Dog), ... } |
| Method implementation | In impl blocks | Internal or external |
| Overloading | Not supported | Supported (different signatures) |
| Default values | Requires #[default] | Write = value directly |
| Heterogeneous containers | Vec<Box<dyn Animal + 'a>> | List(Animal) |
| Dynamic dispatch | Vtable lookup | compile-time type collection |
| Self keyword | Magic keyword, implicit quantification | Explicit 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.
# 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:
- Recognize
Animal(Dog)as an instantiation call of(Self: Type) -> Type - Perform
Self ↦ Dogsubstitution: expandAnimal(Dog)→{ speak: (self: &Dog) -> String } - Check whether
Dogprovides all required methods (signature matching) - If passed → generate implementation proof
- If failed → compilation error
Expansion equivalence:
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
Selfin{ speak: (self: &Self) -> String }is an abstract type parameter, with only syntactic checking performed. - At instantiation point: When
Animal(Dog)is invoked, performSelf ↦ Dogand 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:
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
Dog: Type = {
x: Int = 10,
Animal(Dog),
speak: (self: &Dog) -> String = "Woof", # Method implementation internal
}2.2 External declaration
Dog: Type = {
x: Int,
Animal(Dog),
}
# Method implementation external
Dog.speak: (self: &Dog) -> String = "Woof"2.3 Mixed declaration
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:
- Collect all definitions (internal and external)
- Group by signature (overload)
- Check for overriding (report error)
- Check interface completeness
- Generate implementation proof
3. Overloading and overriding
Core rule:
- Different signatures → overloading → allowed
- Identical signatures → overriding → report error
3.1 Overloading (allowed)
# Different parameter types, overloading allowed
Dog.speak: (self: &Dog) -> String = "Woof"
Dog.speak: (self: &Dog, volume: Int) -> String = "WOOF"3.2 Overriding (prohibited)
# Identical signatures, overriding prohibited
Dog.speak: (self: &Dog) -> String = "Woof"
Dog.speak: (self: &Dog) -> String = "Bark" # ❌ Error: overriding not allowedError 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 definition3.3 Unified rules
Internal and external declarations follow the same overloading/overriding rules:
# 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" # ❌ Error4. Default values
Core rule: Write = value directly after the field, eliminating the need for constructors.
Dog: Type = {
x: Int = 10, # Default value
y: Int = 20, # Default value
Animal(Dog),
}Compiler-generated constructors:
# 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:
Dog: Type = {
x: Int,
y: Int,
Animal(Dog),
}
# External declaration of default values
Dog.x: Int = 10
Dog.y: Int = 20Equivalent to internal declaration.
5. Compiler implementation
5.1 Interface descriptor
// Compiler internal: interface descriptor
struct InterfaceDescriptor {
name: String,
self_param: TypeParam, // Self type parameter
methods: Vec<MethodSignature>,
}5.2 Type definition
// 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
// 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 proof6. 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)".
# 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.
dog = Dog.new()
animals: List(Animal) = [dog]
# dog.speak() ← ❌ Compilation error: dog has been moved6.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:
// 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):
- Encounter
List(Animal)constructed for the first time → generate initial enum (all known constructor types in the current compilation unit) - Each
append/push/ index assignment → check whether the value type is already in the enum; if not, expand the enum variant - Generate monomorphized
matchdispatch code for the final enum - Cross-compilation-unit: rely on LTO (Link-Time Optimization) to merge enum variants. When
Animalas 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:
# 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.
# 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 enumCompiler processing:
- Check the return type of the
appendargument - Verify whether the type implements the target interface
- If passed → expand enum, allow insertion
- 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 method | Vtable pointer → method pointer | Enum match → direct call |
| Runtime overhead | One indirect addressing | branch (can be optimized by CPU branch prediction) |
| compile-time generation | Vtable | Enum + match |
| User annotation | Requires dyn Trait + 'a | Not required |
| ImplementationProof | N/A | Erased 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$Groupvariant types, as pure IR/bytecode/runtime artifacts (instructionsCreateVariant/VariantTag/VariantPayload, runtime valueRuntimeValue::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/VariantPayloadvalidates 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
# 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
# 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
# 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
# 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
# MeowPlugin system
# 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
- Concise: No
implkeyword needed - Flexible: Both internal and external method implementations supported
- Unified: Consistent overloading rules
- Convenient: Concise default value syntax
- Zero overhead: No vtable, compile-time type collection
- Type safe: Interface matching is a compile-time check
- User-transparent: No need to write
dyn Animal + 'a
Disadvantages
- Limitation: No runtime dynamic types (full duck typing) supported
- compile-time overhead: Need to generate enum variants and match dispatch code for each interface
- Type set: Must be fully known at compile time (within a single compilation unit)
Mitigations
- Plugin system: Supported via compile-time interface matching check
- Type set: Ownership tracking, incremental construction—collected at each
append/construction point, not a global scan - Cross-compilation-unit: Link-time merging of enum variant sets, sharing mechanisms with link-time monomorphization
Alternatives
| Plan | Why not chosen |
|---|---|
impl keyword | Increases syntactic complexity |
Vtable (dyn Trait) | Requires brand annotation ('a) |
| Full duck typing | Runtime 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 isanimals → animals[0] → enum_variant → field - Mutable access is achieved indirectly through interface methods, not exposing
&mut AnimalGroupto the user
Interface inheritance
Interfaces can include other interfaces. No new syntax introduced—uses exactly the same syntax position as type declaration of interfaces:
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:
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.
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:
- Type provides → use the type's method
- Type does not provide, interface has default → compiler inlines default implementation onto the type (zero vtable overhead)
- 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
| Phase | Content | Depends on |
|---|---|---|
| Phase 1 | Interface declaration syntax ((Self: Type) -> Type) + Self type parameter | RFC-011 |
| Phase 2 | Interface instantiation (Animal(Dog)) + Self ↦ ConcreteType substitution | Phase 1 |
| Phase 3 | Internal/external declaration of method implementation | Phase 2 |
| Phase 4 | Overloading and overriding rules | Phase 3 |
| Phase 5 | Default value syntax | Phase 3 |
| Phase 6 | Interface inheritance | Phase 4 |
| Phase 7 | Default method implementations | Phase 6 |
| Phase 8 | Implementation proof generation | Phase 7 |
| Phase 9 | compile-time type collection | Phase 8 |
| Phase 10 | Dynamic dispatch implementation | Phase 9 |
Design decision record
| Decision | Decision | Reason | Date |
|---|---|---|---|
| Interface declaration syntax | Interfaces are parameterized types (Self: Type) -> Type, instantiated at implementation | Eliminate Self magic keyword, fully consistent with the RFC-011 generics system | 2026-06-14 |
| Self type parameter | Explicit type parameter; only syntactic check at interface definition, full check at instantiation point | Avoid free type variables in HM inference | 2026-06-14 |
| Dynamic dispatch | compile-time type collection + auto-generated enum | No vtable, zero runtime lookup, user-transparent | 2026-06-14 |
| External method declaration | Supported | Flexibility equivalent to internal declaration; compiler responsible for cross-file collection | 2026-06-14 |
| Overriding | Prohibited (same signature reports error) | Overriding causes unpredictable behavior; overloading covers all cases | 2026-06-14 |
| Interface inheritance | Supported, no new syntax | Same syntax position as type declaration of interfaces. Encourages composition (multi-interface instantiation); discourages deep inheritance trees | 2026-07-03 |
| Default method implementations | Supported, similar to Copy/Clone auto-derive | Interface provides default body; compiler inlines onto implementing type; user can override. No virtual/override introduced | 2026-07-03 |
| Default method constraints | Verify at interface definition: can only reference same-interface methods, cannot assume supertype implementation | Interface is a closed contract. Inheritance can assume subtype implementation, but interfaces cannot assume the implementing type's fields/methods | 2026-07-03 |
| Type collection strategy | Ownership tracking, incremental construction—collected at each append/construction point | Not a global scan of all implementers, but incremental enum expansion by ownership operation point | 2026-07-03 |
| ImplementationProof | Pure compile-time concept, erased at runtime | runtime takes enum match dispatch; proof only used for compile-time verification | 2026-07-03 |
| Cross-compilation-unit | LTO merges enum variants | Existential types passed across compilation unit boundaries; each unit generates partial enum; LTO stage merges | 2026-07-03 |
| Field/method namespace | Unified namespace, conflict reports error | Field access point.x and method call point.x() are syntactically indistinguishable; unification avoids ambiguity | 2026-07-03 |
| Heterogeneous container ownership | Move semantics; original variable unusable after insertion | Consistent with RFC-009 ownership model | 2026-07-03 |
| Brand projection | match pattern binding produces sub-brand, equivalent to field projection | Consistent with RFC-009a brand tree mechanism; enum variant projection is a valid path in the brand tree | 2026-07-03 |
| Receiver spelling convention | &Self borrow / &mut Self mutable borrow / value = Move | Receivers follow signature semantics (RFC-009); interface default is borrow; historical value spelling migrated to &Self | 2026-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.Selfis 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
- RFC-011: generics System Design — Parent RFC
- RFC-009: Ownership Model Design — Ownership system
- RFC-009a: Borrow Proof Pipeline — Brand mechanism
- RFC-010: Unified Type Syntax — Unified syntax
Lifecycle and destination
| Status | Location | Description |
|---|---|---|
| Accepted | docs/design/rfc/accepted/ | Formal design document |
