Traits as language hooks (and overloading)¶
Traits let you describe behavior contracts: “a type supports X”.
Some common language features are easiest to understand as desugaring into trait methods. This is similar in spirit
to Rust using traits like IntoIterator, Index, and Add to power for, indexing, and operators.
This page explains the idea with one concrete example. For the authoritative reference pages, see:
- Derives & traits (Reference)
- Stdlib traits: collection protocols
- Stdlib traits: indexing and slicing
- Stdlib traits: callable objects
- Stdlib traits: operators
- Stdlib traits: conversions
Why this matters¶
This is the mental model behind “ergonomic syntax without magic”:
- You can use pleasant syntax (
x[i],for x in y,a + b) without baking special cases into the language. - User-defined types can become “first-class citizens” by implementing the same hooks as built-in types.
- The compiler can typecheck these capabilities explicitly (Rust-like), instead of relying on runtime duck typing.
What “language hooks” means¶
A language hook is a method name that the compiler can call implicitly when you use a piece of syntax.
Examples:
len(x)can desugar to something likex.__len__()xs[i]can desugar toxs.__getitem__(i)obj()can desugar toobj.__call__(...)a + bcan desugar toa.__add__(b)
The important idea is that the syntax stays simple, while the behavior is defined by traits.
Example: indexing is a hook¶
When you write:
value = xs[i]
Think:
value = xs.__getitem__(i)
So if you want a custom type to support [], you implement the indexing hook method (and the corresponding trait
requirements as defined in the stdlib trait docs):
model Grid:
data: list[int]
def __getitem__(self, idx: int) -> int:
return self.data[idx]
Rust analogy: xs[i] is powered by std::ops::Index, but the idea is the same: syntax → a trait-defined hook.
How this is Rust-like (not Python-magic)¶
In Python, “protocols” are often structural and dynamic (“if it quacks, it’s a duck”), and operator behavior is resolved at runtime via dunder lookup.
In Incan, the intent is closer to Rust:
- Static typing: whether a type supports a hook is part of the type system.
- Deterministic dispatch: behavior is resolved at compile time (no dynamic MRO).
- No runtime patching: you can’t add behavior to types at runtime.
This is what keeps hook-based ergonomics predictable in large codebases.
Overloading: what we mean (and what we don’t)¶
“Overloading” can mean different things:
- Operator overloading:
a + buses a hook like__add__. - Trait-based polymorphism: generic code can accept “anything that implements Trait X”.
It does not necessarily mean “multiple functions with the same name and different signatures” (traditional overload sets).
In Incan, most extensibility is intended to flow through traits and explicit, checkable contracts.
A concrete mental model¶
You can think of hook traits as giving user-defined types the same “surface ergonomics” that built-in types have.
For example, if a type can be indexed, you should be able to write x[i] without caring whether x is a built-in list
or a custom collection type.
That’s why the stdlib defines traits for:
- collection protocols (
len, iteration, membership, truthiness) - indexing and slicing (
[],a:b:c) - callability (
obj()) - operators (
+,-,*,/, etc.) - conversions (
from,into,try_from,try_into)