0%

Section 19: Existential Types (impl Trait)

Goal: Enable returning opaque types that implement a trait without exposing concrete type

Criticality: Low — API design improvement

Dependencies: Section 3 (Traits), Monomorphization infrastructure (for AOT compilation — impl Trait is statically dispatched/monomorphized)

Proposal: proposals/approved/existential-types-proposal.md

Sync Points: Opaque Type Representation (multi-crate sync required)

Adding impl Trait return types requires updates across these crates:

  1. ori_ir — Add Type::ImplTrait { bounds, where_clause } variant in type AST
  2. ori_types — Infer concrete type from function body, verify all return paths yield same type, check trait bounds satisfied, reject invalid positions (arg, struct field)
  3. ori_eval — Evaluator sees concrete type (opaque only to callers), no special handling needed
  4. ori_llvm — Monomorphize to concrete type at codegen time, no vtable needed (static dispatch)

Design Decisions

QuestionDecisionRationale
Syntaximpl Trait where Assoc == TypeType-local where clause for associated types
PositionReturn onlyArgument position uses generics instead
Multiple traitsimpl A + BFlexibility
InferencePer-function, from bodyPredictable
Where clauseType-localConstraints on associated types, not type params

Reference Implementation

Rust

~/projects/reference_repos/lang_repos/rust/compiler/rustc_hir/src/hir.rs     # OpaqueTy definition
~/projects/reference_repos/lang_repos/rust/compiler/rustc_hir_typeck/src/   # Type inference for impl Trait
~/projects/reference_repos/lang_repos/rust/compiler/rustc_middle/src/ty/    # Type representation

Swift

# Swift has `any Protocol` (existential) and `some Protocol` (opaque return)
~/projects/reference_repos/lang_repos/swift/lib/Sema/CSSimplify.cpp         # Existential type solving
~/projects/reference_repos/lang_repos/swift/lib/AST/Type.cpp                # ExistentialType representation

19.1 Return Position impl Trait

Spec section: spec/08-types.md § Existential Types

Syntax

// Return opaque type
@make_iterator (items: [int]) -> impl Iterator where Item == int = {
    items.iter()
}

// Caller sees: impl Iterator where Item == int
// Cannot access concrete type
let iter = make_iterator(items: [1, 2, 3])
for x in iter do print(msg: `{x}`)  // Works via Iterator trait

// Multiple bounds
@make_printable_iterator () -> impl Iterator + Clone where Item == int = ...

Semantics

  • Return type is opaque to caller
  • Compiler knows concrete type internally
  • All return paths must return same concrete type
  • Trait bounds must be satisfied

Implementation

  • Spec: Existential type syntax

    • impl Trait in return position
    • Multiple bounds with +
    • Associated type constraints
    • LLVM Support: LLVM codegen for existential type syntax
    • LLVM Rust Tests: ori_llvm/tests/impl_trait_tests.rs — existential type syntax codegen
    • AOT Tests: No AOT coverage yet
  • Parser: Parse impl Trait

    • In return type position
    • Trait bounds parsing
    • Associated types
    • LLVM Support: LLVM codegen for parsed impl Trait
    • LLVM Rust Tests: ori_llvm/tests/impl_trait_tests.rs — impl Trait parsing codegen
    • AOT Tests: No AOT coverage yet
  • Type checker: Existential type handling

    • Infer concrete type from body
    • Verify all returns same type
    • Check trait bounds satisfied
    • LLVM Support: LLVM codegen for existential type handling
    • LLVM Rust Tests: ori_llvm/tests/impl_trait_tests.rs — existential type handling codegen
    • AOT Tests: No AOT coverage yet
  • Test: tests/spec/types/impl_trait.ori

    • Basic impl Trait return
    • Multiple bounds
    • Associated type constraints
    • LLVM Support: LLVM codegen for impl Trait tests
    • LLVM Rust Tests: ori_llvm/tests/impl_trait_tests.rs — impl Trait tests codegen
    • AOT Tests: No AOT coverage yet

19.2 Type Inference

Spec section: spec/08-types.md § Existential Type Inference

Rules

// Concrete type inferred from function body
@numbers () -> impl Iterator where Item == int = {
    [1, 2, 3].iter()  // Concrete: ListIterator<int>
}

// All return paths must have same concrete type
@maybe_numbers (flag: bool) -> impl Iterator where Item == int = {
    if flag then
        [1, 2, 3].iter()
    else
        [4, 5, 6].iter()  // OK: same concrete type
}

// Error: different concrete types
@bad_numbers (flag: bool) -> impl Iterator where Item == int = {
    if flag then
        [1, 2, 3].iter()       // ListIterator<int>
    else
        (1..10).iter()         // RangeIterator<int>
    // Error: impl Trait returns different types
}

Implementation

  • Spec: Inference rules

    • Single concrete type requirement
    • Branch unification
    • Error messages
    • LLVM Support: LLVM codegen for impl Trait inference rules
    • LLVM Rust Tests: ori_llvm/tests/impl_trait_tests.rs — inference rules codegen
    • AOT Tests: No AOT coverage yet
  • Type checker: Unify return types

    • Track expected opaque type
    • Unify concrete returns
    • Clear error on mismatch
    • LLVM Support: LLVM codegen for return type unification
    • LLVM Rust Tests: ori_llvm/tests/impl_trait_tests.rs — return type unification codegen
    • AOT Tests: No AOT coverage yet
  • Diagnostics: Helpful errors

    • Show both concrete types
    • Suggest Box
    • LLVM Support: LLVM codegen for impl Trait diagnostics
    • LLVM Rust Tests: ori_llvm/tests/impl_trait_tests.rs — diagnostics codegen
    • AOT Tests: No AOT coverage yet
  • Test: tests/spec/types/impl_trait_inference.ori

    • Multiple return paths same type
    • Error on different types
    • LLVM Support: LLVM codegen for impl Trait inference tests
    • LLVM Rust Tests: ori_llvm/tests/impl_trait_tests.rs — impl Trait inference tests codegen
    • AOT Tests: No AOT coverage yet

19.3 Associated Type Constraints

Spec section: spec/08-types.md § Existential Associated Types

Syntax

// Constrain associated type
@int_iterator () -> impl Iterator where Item == int = ...

// Use with other traits
@cloneable_ints () -> impl Iterator + Clone where Item == int = ...

// Multiple associated types
trait Mapping {
    type Key
    type Value
    @get (self, key: Self.Key) -> Option<Self.Value>
}

@string_int_map () -> impl Mapping where Key == str, Value == int = ...

Implementation

  • Spec: Associated type syntax

    • <Assoc = Type> constraint
    • Multiple constraints
    • LLVM Support: LLVM codegen for associated type syntax
    • LLVM Rust Tests: ori_llvm/tests/impl_trait_tests.rs — associated type syntax codegen
    • AOT Tests: No AOT coverage yet
  • Type checker: Validate associated types

    • Match concrete type’s assoc types
    • Error on mismatch
    • LLVM Support: LLVM codegen for associated type validation
    • LLVM Rust Tests: ori_llvm/tests/impl_trait_tests.rs — associated type validation codegen
    • AOT Tests: No AOT coverage yet
  • Test: tests/spec/types/impl_trait_assoc.ori

    • Iterator with Item
    • Custom trait with assoc types
    • LLVM Support: LLVM codegen for associated type tests
    • LLVM Rust Tests: ori_llvm/tests/impl_trait_tests.rs — associated type tests codegen
    • AOT Tests: No AOT coverage yet

19.4 Limitations and Errors

Spec section: spec/08-types.md § Existential Limitations

Not Supported

// Argument position - NOT supported (use generics)
@take_iterator (iter: impl Iterator where Item == int) -> void = ...  // Error
// Correct:
@take_iterator<I: Iterator> (iter: I) -> void where I.Item == int = ...

// In struct fields - NOT supported (use generics)
type Container = {
    iter: impl Iterator where Item == int,  // Error
}
// Correct:
type Container<I: Iterator> = { iter: I } where I.Item == int

Error Messages

error: `impl Trait` is only allowed in return position
  --> src/main.ori:5:20
  |
5 | @foo (x: impl Trait) -> void
  |          ^^^^^^^^^^ impl Trait not allowed here
  |
  = help: use a generic parameter instead: @foo<T: Trait> (x: T) -> void

Implementation

  • Spec: Document limitations

    • Return position only
    • Not in structs
    • Not in traits
    • LLVM Support: LLVM codegen for impl Trait limitations
    • LLVM Rust Tests: ori_llvm/tests/impl_trait_tests.rs — limitations codegen
    • AOT Tests: No AOT coverage yet
  • Type checker: Reject invalid positions

    • Error on arg position
    • Error in struct fields
    • Error in trait methods
    • LLVM Support: LLVM codegen for invalid position rejection
    • LLVM Rust Tests: ori_llvm/tests/impl_trait_tests.rs — invalid position rejection codegen
    • AOT Tests: No AOT coverage yet
  • Diagnostics: Suggest alternatives

    • Generic parameter
    • Associated type
    • LLVM Support: LLVM codegen for alternative suggestions
    • LLVM Rust Tests: ori_llvm/tests/impl_trait_tests.rs — alternative suggestions codegen
    • AOT Tests: No AOT coverage yet
  • Test: tests/compile-fail/types/impl_trait_position.ori

    • Arg position error
    • Struct field error
    • Trait method error
    • LLVM Support: LLVM codegen for position error tests
    • LLVM Rust Tests: ori_llvm/tests/impl_trait_tests.rs — position error tests codegen
    • AOT Tests: No AOT coverage yet

19.5 impl Trait vs dyn Trait

Spec section: spec/08-types.md § Static vs Dynamic Dispatch

Comparison

Featureimpl Traitdyn Trait
DispatchStatic (monomorphized)Dynamic (vtable)
SizeConcrete type sizePointer + vtable
PerformanceBetter (inlined)Overhead
FlexibilityOne concrete typeAny type at runtime
RecursionCannot (infinite size)Can (via Box)

When to Use

// Use impl Trait: single concrete type, performance matters
@fast_iterator () -> impl Iterator where Item == int = [1, 2, 3].iter()

// Use trait object: multiple types possible, flexibility needed
@any_iterator (flag: bool) -> Iterator where Item == int = {
    if flag then
        [1, 2, 3].iter()
    else
        (1..10).iter()
}

Implementation

  • Spec: Compare impl vs dyn

    • Use cases
    • Performance implications
    • When each is appropriate
    • LLVM Support: LLVM codegen for impl vs dyn comparison
    • LLVM Rust Tests: ori_llvm/tests/impl_trait_tests.rs — impl vs dyn comparison codegen
    • AOT Tests: No AOT coverage yet
  • Documentation: Best practices guide

    • Decision flowchart
    • Common patterns
    • LLVM Support: LLVM codegen for best practices examples
    • LLVM Rust Tests: ori_llvm/tests/impl_trait_tests.rs — best practices examples codegen
    • AOT Tests: No AOT coverage yet
  • Test: tests/spec/types/impl_vs_dyn.ori

    • impl Trait usage
    • dyn Trait usage
    • Conversion between them
    • LLVM Support: LLVM codegen for impl vs dyn tests
    • LLVM Rust Tests: ori_llvm/tests/impl_trait_tests.rs — impl vs dyn tests codegen
    • AOT Tests: No AOT coverage yet

Section Completion Checklist

  • All items above have all checkboxes marked [ ]
  • Spec updated: spec/08-types.md existential types section
  • CLAUDE.md updated with impl Trait syntax
  • Return position impl Trait works
  • Type inference correct
  • Associated type constraints work
  • Clear errors for invalid positions
  • All tests pass: ./test-all.sh

Exit Criteria: Can write iterator-returning functions with clean APIs


Example: Iterator Combinators

// Clean API with impl Trait in return position
// Note: impl Trait is only allowed in return position, not argument position
// Use generics for arguments instead

@map<I: Iterator, U> (
    iter: I,
    f: (I.Item) -> U,
) -> impl Iterator where Item == U = {
    MapIterator { inner: iter, transform: f }
}

@filter<I: Iterator> (
    iter: I,
    predicate: (I.Item) -> bool,
) -> impl Iterator where Item == I.Item = {
    FilterIterator { inner: iter, predicate: predicate }
}

@take<I: Iterator> (
    iter: I,
    n: int,
) -> impl Iterator where Item == I.Item = {
    TakeIterator { inner: iter, remaining: n }
}

// Usage - clean, composable
@first_10_even_squares () -> impl Iterator where Item == int = {
    (1..100)
        .filter(predicate: n -> n % 2 == 0)
        .map(transform: n -> n * n)
        .take(count: 10)
}

// Caller doesn't know concrete type (MapIterator<FilterIterator<...>>)
// but can use it as Iterator
let squares = first_10_even_squares()
for sq in squares do print(msg: `{sq}`)