Standard Library — Interfaces
Contents
Overview
The prelude is a set of interfaces, types, enums, and functions that are available in every Leaf source file without an explicit use declaration. The prelude is not a user-authored module — it is built into the language and injected by the compiler before any other imports are resolved.
What the prelude provides:
| Category | Names |
|---|---|
| Interfaces | PartialEq[T], Eq[T], Hash, Comparable[T], ToString, Iterator[T], Iterable[T] |
| Enums | Ordering (Less, Equal, Greater), Option[T] (Some, None), Result[T, E] (Ok, Err), GeneratorResult[Y, R] (Yielded, Done), IoError (Eof, Other) |
| Tuples | Tuple[T1, T2, ...] (2 to 10 elements) |
| Collections | List[T], Map[K, V], Set[T], Buffer |
| Functions | print, println, readln, panic |
| Type sugar | T? → Option[T] |
All built-in primitive types (bool, byte, int, uint, float, str) implement a set of prelude interfaces. The conformance table is in stdlib/functions.md.
Prelude names can be shadowed. Local definitions always take precedence over the prelude in name resolution. A top-level declaration (struct Option, fn print(), etc.) in a .leaf file shadows the prelude name for the entire file. A local variable binding shadows a prelude name within its enclosing scope, following normal scoping rules. In both cases, the prelude version becomes inaccessible in that scope — there is no qualified path to force access to a shadowed prelude name.
⚠️ WARNING: Shadowing prelude names cannot be undone. If you define
type Option[T] = ...orfn println(s: str) = ...at the top level of a file, you cannot access the built-in versions anywhere in that file. There is no way to "un-shadow" or use qualified paths to reach the prelude. Choose local names carefully to avoid conflicts with commonly-used prelude types and functions.
Built-in Interfaces
User-defined structs must explicitly declare which interfaces they satisfy using the implements keyword. Conforming methods must match the visibility of the interface they implement — a pub interface requires a pub method, a pkg interface requires a pkg method, and a private interface requires a private method. See lang/structs/core.md §4.5 for full visibility rules. Built-in primitive types (bool, byte, int, uint, float, str) and built-in generic types (Option[T], Result[T, E], List[T], Map[K, V], Buffer, Tuple[...], Generator[Y, R, N]) have compiler-provided interface conformance (see the Interface Conformance Summary in stdlib/functions.md).
PartialEq[T]
interface PartialEq[in T]
fn eq(self, other: T) -> bool
end
Gates the == and != operators. a == b desugars to a.eq(b). a != b desugars to !a.eq(b).
PartialEq does not require reflexivity — x.eq(x) may return false for some values (notably float NaN). Code that needs a reflexivity guarantee should constrain on Eq[T] instead (see Eq[T] below).
Built-in conformance: bool, byte, int, uint, float, str.
float implements PartialEq[float] with IEEE 754 semantics: NaN != NaN and -0.0 == 0.0. See lang/semantics/values.md §2.11.2 for details.
Enum conformance: All enum types have built-in equality. For enums with associated data, equality compares both the variant tag and the associated values — the associated value types must themselves satisfy PartialEq.
User-defined conformance:
struct Point implements PartialEq[Point]
pub x: int
pub y: int
pub fn eq(self, other: Self) -> bool
return self.x == other.x && self.y == other.y
end
end
Point { x: 1, y: 2 } == Point { x: 1, y: 2 } # true
Without an eq method, using == or != on a struct is a type error.
Eq[T]
interface Eq[in T] where Self: PartialEq[T]
end
A marker interface that refines PartialEq[T] with a reflexivity guarantee: for any value x of type T: Eq[T], the expression x == x must return true. This allows generic code to rely on reflexive equality.
Eq[T] has no methods of its own. It is a supertrait of PartialEq[T] — any type implementing Eq[T] must also implement PartialEq[T]. The where Self: PartialEq[T] clause is a supertrait constraint: the compiler verifies that the implementing type satisfies PartialEq[T] whenever Eq[T] is declared.
Built-in conformance: bool, byte, int, uint, str.
float does not implement Eq[float] because IEEE 754 NaN violates reflexivity (NaN != NaN). Generic code constrained by T: Eq[T] cannot use float as a type argument.
User-defined conformance:
struct Point implements Eq[Point]
pub x: int
pub y: int
pub fn eq(self, other: Self) -> bool
return self.x == other.x && self.y == other.y
end
end
Declaring implements Eq[Point] requires that Point also satisfies PartialEq[Point] (the supertrait). The eq method satisfies PartialEq; the Eq declaration adds the reflexivity guarantee. There is no need to list both PartialEq[Point] and Eq[Point] in the implements clause — declaring Eq[Point] implies PartialEq[Point].
Hash
interface Hash
fn hash(self) -> int
end
Produces an integer hash code for a value. Required for use as a key in Map[K, V].
Contract: If a.eq(b) is true, then a.hash() must equal b.hash(). The converse is not required — unequal values may have equal hashes. Violating this contract produces unspecified behavior for map operations — programs must not rely on any particular outcome when the contract is violated.
Mutation warning: If a struct is used as a Map key or Set element, programs must not mutate it in ways that change its hash() or eq() behavior while it is stored in the collection. Doing so silently corrupts the collection's internal structure — subsequent lookups, insertions, and removals may produce incorrect results. Built-in primitive types (int, uint, float, byte, str, bool) are value types and are copied into collections, so this concern applies only to struct keys and elements.
Implication: Any type that implements Hash should also implement PartialEq. The compiler does not enforce this at the type level, but map operations depend on both.
Built-in conformance: bool, byte, int, uint, float, str.
Enum conformance: Enum types have built-in Hash when all of their associated data types satisfy Hash. For enums with no associated data (simple enums), Hash is unconditional. For enums with associated data, the hash incorporates both the variant tag and the hash of the associated values.
In particular, Option[T] satisfies Hash when T: Hash. This means Option[T] values can be used as map keys when T is hashable.
User-defined conformance:
struct Point implements Eq[Point], Hash
pub x: int
pub y: int
pub fn eq(self, other: Self) -> bool
return self.x == other.x && self.y == other.y
end
pub fn hash(self) -> int
# A simple hash combining scheme — implementation-defined
return self.x * 31 + self.y
end
end
m: Map[Point, str] = { [Point { x: 1, y: 2 }]: "origin" }
Comparable[T]
interface Comparable[in T] where Self: PartialEq[T]
fn compare(self, other: T) -> Ordering
end
Gates the ordering operators <, >, <=, >=. The where Self: PartialEq[T] supertrait constraint ensures that any type supporting ordering also supports == and !=. Declaring implements Comparable[T] implies PartialEq[T] — there is no need to list both in the implements clause.
Consistency contract: If a.eq(b) is true, then a.compare(b) must return Ordering.Equal. Violating this contract produces unspecified behavior for operations that depend on both equality and ordering.
The compare method returns an Ordering enum value (see stdlib/types.md):
| Return value | Meaning |
|---|---|
Ordering.Less |
self is less than other |
Ordering.Equal |
self is equal to other |
Ordering.Greater |
self is greater than other |
Operator desugaring:
| Expression | Desugars to |
|---|---|
a < b |
a.compare(b) == Ordering.Less |
a > b |
a.compare(b) == Ordering.Greater |
a <= b |
a.compare(b) != Ordering.Greater |
a >= b |
a.compare(b) != Ordering.Less |
floatexception: When both operands are statically typed asfloat, the comparison operators use native IEEE 754 intrinsics and do not desugar throughcompare. This ensures correct NaN behavior: all comparisons involving NaN returnfalse. In generic code constrained byT: Comparable[T], operators always desugar throughcompare— even whenTis instantiated withfloat— giving consistent total-ordering semantics. Thefloat.comparemethod provides a total ordering where NaN sorts after all other values — seelang/semantics/values.md§2.11.2.
Built-in conformance: byte, int, uint, float, str.
byte,int, anduintcompare numerically.floatcompares numerically. Thecomparemethod uses a total ordering where NaN sorts after all other values (including positive infinity), and-0.0equals0.0. Note that float comparison operators bypass this method and use IEEE 754 intrinsics — see the exception note above.strcompares lexicographically by byte value.booldoes not implementComparable.
User-defined conformance:
struct Temperature implements Comparable[Temperature]
pub celsius: float
pub fn eq(self, other: Self) -> bool
return self.celsius == other.celsius
end
pub fn compare(self, other: Self) -> Ordering
return self.celsius.compare(other.celsius)
end
end
boiling = Temperature { celsius: 100.0 }
freezing = Temperature { celsius: 0.0 }
boiling > freezing # true
Generic use:
fn max[T: Comparable[T]](a: T, b: T) -> T
if a > b
return a
end
return b
end
fn sort[T: Comparable[T]](items: List[T]) -> List[T]
# ...sorting implementation using < or compare...
end
Direct use of Ordering:
result = 3.compare(5)
match result
Ordering.Less then println("3 is less than 5")
Ordering.Equal then println("equal")
Ordering.Greater then println("3 is greater than 5")
end
ToString
interface ToString
fn toString(self) -> str
end
Gates string interpolation. An expression inside {...} in a string literal must have a type that satisfies ToString.
Built-in conformance: bool, byte, int, uint, float, str.
byteproduces a base-10 decimal representation (e.g.,"42","255").intproduces a base-10 decimal representation (e.g.,"42","-7").uintproduces a base-10 decimal representation (e.g.,"42","0").floatproduces a decimal representation (e.g.,"3.14","-0.5"). The exact formatting (number of decimal places, scientific notation threshold) is implementation-defined. Special values produce"NaN","Infinity", or"-Infinity".strreturns itself.boolproduces"true"or"false".
Enum auto-conformance: Simple enums (enums with no associated data) satisfy ToString unconditionally. For enums with associated data, ToString conformance is conditional — all associated data types must themselves satisfy ToString. For example, Option[T] satisfies ToString when T: ToString, and Result[T, E] satisfies ToString when T: ToString and E: ToString. The compiler generates a string representation that includes the variant name and, for variants with associated data, the string representation of the contained values. The exact output format is unspecified, unstable across compiler releases, and intended only as a developer-facing diagnostic aid — not for user-facing display or serialization.
User-defined conformance:
struct Color implements ToString
r: int
g: int
b: int
pub fn toString(self) -> str
return "rgb({self.r}, {self.g}, {self.b})"
end
end
c = Color { r: 255, g: 128, b: 0 }
println("color: {c}") # "color: rgb(255, 128, 0)"
Iterator[T]
interface Iterator[out T]
fn next(mut self) -> Option[T]
end
An Iterator[T] is a stateful object that produces a sequence of values one at a time. Each call to .next() returns Some(value) for the next element, or None when the sequence is exhausted. Once .next() returns None, subsequent calls must also return None.
Iterator[T] satisfies Iterable[T]. Every iterator is itself iterable — its iter() method returns self. This means iterators can be used directly in for loops and support all default Iterable methods (map, filter, etc.), enabling method chaining on lazy pipelines. Use the toList(), toMap(), or toSet() free functions (in stdlib/functions.md) to materialize the result into the desired collection type:
items.map(fn(x) x * 2).filter(fn(x) x > 5) |> toList
Built-in conformance: Generator[T, (), never] satisfies Iterator[T] structurally. Its .next(value: Option[never]) method is compatible with Iterator[T]'s .next() -> Option[T] because Option[never] can only be None (no Some(never) is constructible), so the compiler always passes None and elides the argument. GeneratorResult[T, ()] is isomorphic to Option[T] — the compiler synthesizes the translation: Yielded(value) becomes Some(value), and Done(()) becomes None. List[T] and Map[K, V] produce iterators through their iter() methods (see Iterable[T] below).
Iterable[T]
interface Iterable[out T]
fn iter(self) -> Iterator[T]
fn map[U](self, f: fn(T) -> U) -> Iterator[U] # default
fn flatMap[U](self, f: fn(T) -> Iterable[U]) -> Iterator[U] # default
fn filter(self, f: fn(T) -> bool) -> Iterator[T] # default
fn reduce[U](self, init: U, f: fn(U, T) -> U) -> U # default
fn find(self, f: fn(T) -> bool) -> Option[T] # default
fn any(self, f: fn(T) -> bool) -> bool # default
fn all(self, f: fn(T) -> bool) -> bool # default
fn each(self, f: fn(T) -> ()) # default
fn join(self, separator: str) -> str where T: ToString # default
fn enumerate(self) -> Iterator[Tuple[uint, T]] # default
end
Any type that implements Iterable[T] can be used in a for loop and gains all of the default iteration methods above. The iter() method is the only required method — everything else has a default implementation.
Default iteration methods:
| Signature | Description |
|---|---|
fn map[U](self, f: fn(T) -> U) -> Iterator[U] |
Return an iterator with f applied to each element. |
fn flatMap[U](self, f: fn(T) -> Iterable[U]) -> Iterator[U] |
Map each element to an iterable, then flatten. |
fn filter(self, f: fn(T) -> bool) -> Iterator[T] |
Return an iterator of elements for which f returns true. |
fn reduce[U](self, init: U, f: fn(U, T) -> U) -> U |
Left fold over elements. |
fn find(self, f: fn(T) -> bool) -> Option[T] |
Return the first element for which f returns true, or None. |
fn any(self, f: fn(T) -> bool) -> bool |
true if f returns true for any element. |
fn all(self, f: fn(T) -> bool) -> bool |
true if f returns true for all elements. |
fn each(self, f: fn(T) -> ()) |
Call f for each element. Returns nothing. |
fn join(self, separator: str) -> str |
Concatenate elements with separator. Requires T: ToString. |
fn enumerate(self) -> Iterator[Tuple[uint, T]] |
Return an iterator of (index, element) pairs, where the index starts at 0 and increments by 1 for each element. |
These methods are available on every Iterable type — List[T], Map[K, V], Set[T], generators, and user-defined types. For Map[K, V], T is Tuple[K, V], so callbacks receive key-value pairs.
Lazy semantics. The map, filter, flatMap, and enumerate methods return Iterator, not List — they are lazy, applying transformations on demand as elements are pulled from the returned iterator. Use the toList(), toMap(), or toSet() free functions (in stdlib/functions.md) to materialize a lazy pipeline into the desired collection type:
doubledEvens = items.map(fn(x) x * 2)
.filter(fn(x) x > 5) |> toList # List[int]
Built-in conformance: List[T] satisfies Iterable[T] (iterates elements), Map[K, V] satisfies Iterable[Tuple[K, V]] (iterates key-value pairs), and Generator[T, (), never] satisfies Iterable[T].
User-defined conformance:
A struct can satisfy Iterable[T] by declaring implements Iterable[T] and providing an iter method that returns an Iterator[T]. All default methods are inherited automatically. The simplest way is to use a gen fn that returns Generator[T], since Generator[T, (), never] satisfies Iterator[T]:
struct Range implements Iterable[int]
pub start: int
pub stop: int
pub gen fn iter(self) -> Generator[int]
mut i = self.start
while i < self.stop
yield i
i += 1
end
end
end
for i in Range { start: 0, stop: 5 }
println("{i}")
end
# All default methods are available:
Range { start: 1, stop: 6 }.map(fn(x) x * 2) |> toList # [2, 4, 6, 8, 10]
Range { start: 1, stop: 6 }.filter(fn(x) x > 3) |> toList # [4, 5]
A gen fn always returns Generator[Y, R, N]. When R and N take their default values (() and never respectively — see lang/structs/core.md §4.4), the resulting Generator[T, (), never] satisfies Iterator[T] structurally (its .next(value: Option[never]) method is compatible because Option[never] can only be None and GeneratorResult[T, ()] is isomorphic to Option[T]), so it can be used wherever Iterator[T] is expected.
Variance: Iterable is covariant in T (out T). If Cat structurally conforms to Animal, then Iterable[Cat] is assignable to Iterable[Animal]. Because never is the bottom type, Iterable[never] is assignable to any Iterable[T]. The default methods preserve covariance — callback parameters like fn(T) -> bool place T in a covariant position (contravariant parameter of a contravariant parameter).