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:
- Return type variants: What return types are valid?
- Multiple keys: How to support different key types?
- Error handling: When should Index panic vs return Option?
- Hash shorthand: Does
#work with custom Index? - 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:
- Return type variants
- Multiple key type support
- Standard implementations
- Hash shorthand limitation
Update 06-types.md
Cross-reference to Index trait.
Summary
| Aspect | Details |
|---|---|
| Syntax | x[key] desugars to x.index(key: key) |
| Trait | trait Index<Key, Value> { @index (self, key: Key) -> Value } |
| Return types | T (panic), Option<T> (none), Result<T, E> (error) |
| Multiple keys | Implement Index for each key type |
| Hash shorthand | Built-in only, not for custom Index |
| Assignment | Supported 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] = xdesugars tolist = list.updated(key: i, value: x)using theIndexSettrait. The original rejection reasoning conflated in-place mutation with reassignment — Ori’s mutable bindings support reassignment without mutable references. The recommended alternativelist.set(index:, value:)was never implemented;updated(key:, value:)is the approved method name.