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] = ... or fn 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

float exception: When both operands are statically typed as float, the comparison operators use native IEEE 754 intrinsics and do not desugar through compare. This ensures correct NaN behavior: all comparisons involving NaN return false. In generic code constrained by T: Comparable[T], operators always desugar through compare — even when T is instantiated with float — giving consistent total-ordering semantics. The float.compare method provides a total ordering where NaN sorts after all other values — see lang/semantics/values.md §2.11.2.

Built-in conformance: byte, int, uint, float, str.

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.

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).


Link copied to clipboard!