Proposal: Index Trait

Status: Approved Approved: 2026-01-30 Author: Eric (with AI assistance) Created: 2026-01-30 Affects: Compiler, type system, expressions


Summary

This proposal formalizes the Index trait for custom subscripting, including return type variants, multiple key types, and error handling patterns.


Problem Statement

The spec shows Index trait usage but leaves unclear:

  1. Return type variants: What return types are valid?
  2. Multiple keys: How to support different key types?
  3. Error handling: When should Index panic vs return Option?
  4. Hash shorthand: Does # work with custom Index?
  5. Assignment: Can Index support x[k] = v?

Definition

trait Index<Key, Value> {
    @index (self, key: Key) -> Value
}

The Index trait enables subscript syntax container[key].


Desugaring

x[key]
// Desugars to:
x.index(key: key)

Return Type Variants

Direct Value (Panic on Missing)

For containers where access should always succeed:

impl [T]: Index<int, T> {
    @index (self, key: int) -> T  // Panics if out of bounds
}

let list = [1, 2, 3]
list[0]   // 1
list[10]  // panic: index out of bounds

Option (Missing Returns None)

For containers where keys may be absent:

impl {K: V}: Index<K, Option<V>> {
    @index (self, key: K) -> Option<V>  // None if not present
}

let map = {"a": 1, "b": 2}
map["a"]  // Some(1)
map["z"]  // None

Result (Detailed Error)

For containers where access can fail with details:

// Example: user-defined JSON type with Result-based indexing
type JsonError = KeyNotFound { key: str } | NotAnObject

impl JsonValue: Index<str, Result<JsonValue, JsonError>> {
    @index (self, key: str) -> Result<JsonValue, JsonError>
}

let json = parse_json(text)?
json["field"]?  // Propagate JsonError if field missing

Multiple Key Types

A type can implement Index for multiple key types:

impl JsonValue: Index<int, Option<JsonValue>> {
    @index (self, key: int) -> Option<JsonValue> = ...
}

impl JsonValue: Index<str, Option<JsonValue>> {
    @index (self, key: str) -> Option<JsonValue> = ...
}

let json = get_json()
json[0]       // Array access
json["key"]   // Object access

Type Inference

The key type must be unambiguous:

json[key]  // OK if key has known type
json[0]    // OK: int literal
json["x"]  // OK: str literal

Ambiguous cases are compile errors:

let key = ???  // Unknown type
json[key]  // ERROR: ambiguous Index implementation

Standard Implementations

List

impl<T> [T]: Index<int, T> {
    @index (self, key: int) -> T =
        if key < 0 || key >= len(collection: self) then
            panic(msg: "index out of bounds")
        else
            // intrinsic: compiler-provided
}

Fixed-Capacity List

impl<T, $N: int> [T, max N]: Index<int, T> {
    @index (self, key: int) -> T = ...  // Same as [T]
}

Map

impl<K: Eq + Hashable, V> {K: V}: Index<K, Option<V>> {
    @index (self, key: K) -> Option<V> = ...
}

String

impl str: Index<int, str> {
    @index (self, key: int) -> str =  // Returns single-codepoint str
        if key < 0 || key >= len(collection: self) then
            panic(msg: "index out of bounds")
        else
            // intrinsic: compiler-provided
}

Hash Shorthand

The # shorthand for length is built-in and does NOT work with custom Index:

let list = [1, 2, 3]
list[# - 1]  // OK: built-in list indexing

type MyContainer = { ... }
impl MyContainer: Index<int, int> { ... }

let c = MyContainer { ... }
c[# - 1]  // ERROR: # not available for custom Index
c[len(collection: c) - 1]  // Use explicit len()

Rationale

# is syntactic sugar that requires compiler knowledge of container length semantics. Custom containers should use len() explicitly.


No Index Assignment

Ori does not support index assignment syntax:

list[0] = 42  // ERROR: assignment via index not supported

Instead, use methods that return modified collections:

let new_list = list.set(index: 0, value: 42)

Rationale

Ori’s memory model prefers immutable updates. Index assignment would require mutable references, which Ori avoids.


Custom Index Examples

Matrix

type Matrix = { rows: [[float]] }

impl Matrix: Index<(int, int), float> {
    @index (self, key: (int, int)) -> float = {
        let (row, col) = key
        self.rows[row][col]
    }
}

let m = Matrix { rows: [[1.0, 2.0], [3.0, 4.0]] }
m[(0, 1)]  // 2.0

Sparse Array

type SparseArray<T> = { data: {int: T}, default: T }

impl<T: Clone> SparseArray<T>: Index<int, T> {
    @index (self, key: int) -> T = match self.data[key] {
        Some(v) -> v
        None -> self.default.clone()
    }
}

Config with Path

type Config = { data: {str: JsonValue} }

impl Config: Index<str, Option<JsonValue>> {
    @index (self, key: str) -> Option<JsonValue> = {
        let parts = key.split(sep: ".")
        parts.fold(
            initial: Some(JsonValue.Object(self.data))
            combine: (acc, part) -> match acc {
                Some(JsonValue.Object(obj)) -> obj[part]
                _ -> None
            }
        )
    }
}

let config = load_config()
config["database.host"]  // Navigate nested path

Type Errors

Wrong Key Type

error[E0950]: mismatched types in index expression
  --> src/main.ori:5:10
   |
 5 | let x = list["key"]
   |              ^^^^^ expected `int`, found `str`
   |
   = note: `[int]` implements `Index<int, int>`, not `Index<str, _>`

No Index Implementation

error[E0951]: `MyType` cannot be indexed
  --> src/main.ori:5:10
   |
 5 | let x = my_value[0]
   |         ^^^^^^^^^^^ `Index` not implemented
   |
   = help: implement `Index<int, _>` for `MyType`: `impl MyType: Index<int, _> { ... }`

Ambiguous Key Type

error[E0952]: ambiguous index key type
  --> src/main.ori:5:10
   |
 5 | let x = json[key]
   |              ^^^ cannot infer key type
   |
   = note: `JsonValue` implements both `Index<int, _>` and `Index<str, _>`
   = help: add type annotation: `let key: int = ...`

Spec Changes Required

Update 09-expressions.md

Expand Index Trait section with:

  1. Return type variants
  2. Multiple key type support
  3. Standard implementations
  4. Hash shorthand limitation

Update 06-types.md

Cross-reference to Index trait.


Summary

AspectDetails
Syntaxx[key] desugars to x.index(key: key)
Traittrait Index<Key, Value> { @index (self, key: Key) -> Value }
Return typesT (panic), Option<T> (none), Result<T, E> (error)
Multiple keysImplement Index for each key type
Hash shorthandBuilt-in only, not for custom Index
AssignmentSupported via IndexSet trait — see index-assignment-proposal

Errata (2026-02-17)

Superseded by index-assignment-proposal: The “No Index Assignment” section above is stale. Index and field assignment have been approved via copy-on-write desugaring: list[i] = x desugars to list = list.updated(key: i, value: x) using the IndexSet trait. The original rejection reasoning conflated in-place mutation with reassignment — Ori’s mutable bindings support reassignment without mutable references. The recommended alternative list.set(index:, value:) was never implemented; updated(key:, value:) is the approved method name.