Proposal: Colon Syntax for Trait Implementations

Status: Approved Author: Eric (with Claude) Created: 2026-03-05 Approved: 2026-03-05 Affects: Parser, spec, grammar, all impl Trait for Type syntax, capability-unification-generics-proposal


Summary

Replace impl Trait for Type with impl Type: Trait, putting the subject before the predicate and unifying impl syntax with every other : conformance position in the language.

BeforeAfter
impl Eq for Point { ... }impl Point: Eq { ... }
impl<T: Eq> Eq for [T] { ... }impl<T: Eq> [T]: Eq { ... }
impl Add<int> for Vector2 { ... }impl Vector2: Add<int> { ... }
impl Point { ... } (inherent)impl Point { ... } (unchanged)
pub def impl Trait { ... }pub def impl Trait { ... } (unchanged)

Motivation

The Problem: One Relationship, Two Syntaxes

After the capability unification proposal (approved 2026-02-20, revised 2026-03-04), : means “conforms to” everywhere in the language:

type Point: Eq, Hashable = { x: int, y: int }   // type declaration
T: Comparable                                      // generic bound
where T: Eq                                        // where clause
trait Comparable: Eq                                // supertrait

But trait implementations break the pattern:

impl Eq for Point { ... }    // "for" instead of ":"

This is the only place where the conformance relationship uses a different keyword. A developer who has internalized “: means conforms to” must remember that impl blocks are the exception.

Subject-First Ordering

Every other conformance position puts the subject first:

ContextSyntaxReading
Type declarationtype Point: Eq”Point conforms to Eq”
Generic boundT: Eq”T conforms to Eq”
Where clausewhere T: Eq”where T conforms to Eq”
Supertraittrait Comparable: Eq”Comparable requires Eq”
Impl (current)impl Eq for Point”implement Eq for Point”
Impl (proposed)impl Point: Eq”implement Point conforms to Eq”

The current impl Eq for Point puts the trait first, then the type — the opposite of every other position. The proposed syntax aligns impl with the universal pattern.

Prior Art

Languages that use : for conformance in implementations:

LanguageSyntaxNotes
Swiftextension Type: Protocol { ... }Exact analogue
Kotlinclass Type : Interface { ... }On class declarations
C#class Type : Interface { ... }On class declarations
C++class Type : public Base { ... }Inheritance

Languages that use keywords:

LanguageSyntax
Rustimpl Trait for Type { ... }
Haskellinstance Class Type where ...

The : approach is more common in the broader language landscape. Rust’s for is somewhat unusual — inherited from Haskell’s instance pattern but with different keyword choice.

Keyword Simplification

for currently has two meanings:

  1. Loop iteration: for x in items do ...
  2. Impl target: impl Trait for Type

After this change, for is purely a loop keyword. One keyword, one meaning. for remains a reserved keyword.


Design

Grammar Change

Current (grammar.ebnf line 299):

trait_impl = "impl" [ generics ] type_path "for" type [ where_clause ] "{" { method } "}" .

Proposed:

trait_impl = "impl" [ generics ] type ":" type_path [ where_clause ] "{" { method } "}" .

Two changes:

  1. "for" becomes ":"
  2. The target type moves before the trait (subject-first ordering)

The inherent_impl and def_impl productions are unchanged:

inherent_impl = "impl" [ generics ] type_path [ where_clause ] "{" { method } "}" .
def_impl      = "def" "impl" identifier "{" { def_impl_method } "}" .

Parsing

Parsing becomes slightly simpler. After impl + optional generics, the parser reads a type expression:

  • If : follows: trait impl — parse the trait name, then {
  • If { follows: inherent impl

The current grammar requires the parser to determine whether the first type expression after impl is a trait (if for follows) or the target type (if { follows). The proposed grammar always reads the target type first — no ambiguity about what the first type expression represents.

Examples

Simple trait impl:

impl Point: Eq {
    @equals (self, other: Self) -> bool = self.x == other.x && self.y == other.y
}

Generic trait impl:

impl<T: Eq> [T]: Eq {
    @equals (self, other: Self) -> bool = {
        if self.len() != other.len() then false
        else self.iter().zip(iter: other.iter()).all(predicate: (a, b) -> a == b)
    }
}

Parameterized trait:

impl Vector2: Add<int> {
    type Output = Vector2
    @add (self, other: int) -> Vector2 = Vector2 { x: self.x + other, y: self.y + other }
}

Multiple impls for same type (visual grouping):

impl Point: Eq { ... }
impl Point: Comparable { ... }
impl Point: Hashable { ... }
impl Point: Printable { ... }

Note how the repeated impl Point: prefix makes it immediately clear these all belong to the same type — compared to the current syntax where the type appears at the end of each line.

Inherent impl (unchanged):

impl Point {
    @new (x: int, y: int) -> Point = Point { x, y }
    @distance (self, other: Point) -> float = { ... }
}

Default impl (unchanged):

pub def impl Printable {
    @to_str (self) -> str = self.debug()
}

Full Consistency Table

After this change, every conformance relationship in Ori uses ::

ContextSyntaxSubject:Predicate
Type declarationtype Point: Eq = { ... }Point:Eq
Generic bound<T: Eq>T:Eq
Where clausewhere T: EqT:Eq
Supertraittrait Comparable: EqComparable:Eq
Trait implimpl Point: Eq { ... }Point:Eq

Five positions, one symbol, one meaning: “conforms to.”


Interaction with Existing Features

def impl — No Change

Default impls have no target type, so no : applies:

pub def impl Debug {
    @debug (self) -> str = `{self}`
}

Error Messages

Diagnostics that reference impl syntax should be updated:

// Before
= help: implement `Iterator` manually: `impl Iterator for MyIter { ... }`

// After
= help: implement `Iterator` manually: `impl MyIter: Iterator { ... }`

The common mistake detector in ori_parse/src/error/mistakes.rs currently suggests impl Trait for Type { ... } when detecting Java’s implements keyword — this must be updated to suggest impl Type: Trait { ... }.

extend — No Change

Extensions use a different syntax and are unaffected:

extend Point {
    @manhattan (self) -> int = self.x.abs() + self.y.abs()
}

Relationship to Capability Unification Proposal

The capability-unification-generics-proposal (approved 2026-02-20) explicitly states that impl Trait for Type is “unaffected” (section 12.1) and marks it as “Unchanged” in its summary table. This proposal supersedes that claim — impl syntax is now part of the : unification. An errata will be added to the capability-unification proposal.


Migration

Scope

This is a syntactic change to the impl block header. The body of impl blocks is unchanged.

Affected Files

  • Grammar: grammar.ebnftrait_impl production rule
  • Spec: 22 spec files with 45+ code examples
  • Proposals: 43 approved proposals with 166 references (errata for each)
  • Tests: 34 spec test files with 120 occurrences
  • Stdlib: library/std/ impl blocks
  • Compiler: Parser (impl parsing in ori_parse/src/grammar/item/impl_def/), type checker comments, error messages (error/mistakes.rs common mistake detector)
  • Rules: .claude/rules/ori-syntax.md

Transition

Since the compiler does not yet implement most trait impls (they are derived or built-in), the migration surface in actual .ori code is minimal. The primary work is updating spec, grammar, proposals, and error message strings.


Alternatives Considered

Keep for

Status quo. The only conformance position using a different keyword. The inconsistency is small but permanent — every new Ori developer must learn “: means conforms-to, except in impl blocks where you use for.”

Use as instead of :

impl Point as Eq { ... } — reads well (“implement Point as Eq”) but introduces a third syntax for the same relationship. as also already means type conversion (42 as float), so this would overload it.

Reverse order: impl Trait: Type

impl Eq: Point { ... } — puts the trait first like the current syntax but uses :. Rejected because : everywhere else means “left conforms to right,” and Eq: Point reads as “Eq conforms to Point” — backwards.


Summary Table

AspectDetail
Keyword removed from implfor (remains reserved for loop syntax)
Keyword addedNone (: already exists)
Grammar changeOne production rule (trait_impl)
Parsing complexitySlightly reduced (no trait-vs-type ambiguity)
ConsistencyCompletes the : = “conforms to” unification
Breaking changeYes (syntactic, all impl Trait for Typeimpl Type: Trait)
Migration effortMedium (spec/grammar/proposals/tests/compiler; minimal .ori code)