eq
stdlib/eq.hk: the Eq trait and its builtin impls.
==/!= always dispatch through Eq: a concrete type either has an Eq impl (a builtin below, a hand-written one, or one synthesised on demand: the same @derive(Eq) expansion, which calls .eq?() recursively on each field / variant payload), or comparing it is a compile error. There is no structural runtime fallback, and the two execution tiers cannot disagree (HANKI.md §20).
Impl bodies use ==, which the type-aware comparison lowering routes to the matching opcode: Op::EqInt for the fixed-width ints, Op::EqBigInt for int, Op::EqStr for string, Op::EqBytes for bytes, Op::EqBool for bool. The trait surface is uniform; the per-impl opcode choice happens automatically based on operand type.
All integer types are covered (int plus i8/i16/i32/i64 and u8, u16, u32, u64), and a struct or sum with any integer field can derive Eq. f64 is omitted: IEEE == makes NaN != NaN, which would break Eq reflexivity. With no structural fallback, an aggregate holding an f64 therefore has no Eq at all, and comparing it is a compile error naming the field, which closes the nested-float bit-compare divergence at the root (HANKI.md §22).
The container impls (Eq<Option<T>>, Eq<List<T>>) live here, beside the trait, the way Encode<Option<T>> sits in encode.hk: the trait file opens the container and never the other way round, and there is no import cycle. Eq<Map<K, V>>, order-independent, is in map.hk, which is the module that defines Map.
Eq
trait Eq
eq?
def eq?(self, other: Self) -> bool
Structural equality. a.eq?(b) is what the == operator dispatches to for a type with an Eq impl, and the two always agree.
5i32.eq?(5i32) => true
5i32.eq?(7i32) => false
impl Eq<i32>
eq?
def eq?(self, other: Self) -> bool
Value equality for i32.
0i32.eq?(0i32) => true
5i32.eq?(7i32) => false
impl Eq<bool>
eq?
def eq?(self, other: Self) -> bool
Value equality for bool.
true.eq?(true) => true
true.eq?(false) => false
impl Eq<string>
eq?
def eq?(self, other: Self) -> bool
Value equality for string.
"hi".eq?("hi") => true
"hi".eq?("bye") => false
impl Eq<bytes>
eq?
def eq?(self, other: Self) -> bool
Value equality for bytes.
"hi".to_bytes().eq?("hi".to_bytes()) => true
"hi".to_bytes().eq?("bye".to_bytes()) => false
impl Eq<decimal>
eq?
def eq?(self, other: Self) -> bool
decimal compares by value through the tier's own opcode, and 1.5 and 1.50 are equal despite differing scales. The card's §3 already promises this tier reflexive equality and a total order; the impl is what lets a struct holding one derive Eq in place of only comparing directly.
1.5.eq?(1.50) => true
1.5.eq?(2.5) => false
impl Eq<rational>
eq?
def eq?(self, other: Self) -> bool
As Eq<decimal>, and reduced on construction, and 3/2 and 6/4 are one value by every measure.
a: rational = 3 / 2
b: rational = 6 / 4
a.eq?(b) => true
impl Eq<int>
eq?
def eq?(self, other: Self) -> bool
int is the default integer (arbitrary precision); == lowers to Op::EqBigInt.
0.eq?(0) => true
5.eq?(7) => false
impl Eq<()>
eq?
def eq?(self, other: Self) -> bool
() is a singleton: there is one value and it equals itself, and equality is total and trivially reflexive. This is what makes Result<(), E> and Option<()> comparable, the form every try_each!-style action returns.
().eq?(()) => true
impl Eq<i8>
eq?
def eq?(self, other: Self) -> bool
Value equality for i8.
0i8.eq?(0i8) => true
5i8.eq?(7i8) => false
impl Eq<i16>
eq?
def eq?(self, other: Self) -> bool
Value equality for i16.
0i16.eq?(0i16) => true
5i16.eq?(7i16) => false
impl Eq<i64>
eq?
def eq?(self, other: Self) -> bool
Value equality for i64.
0i64.eq?(0i64) => true
5i64.eq?(7i64) => false
impl Eq<u8>
eq?
def eq?(self, other: Self) -> bool
Value equality for u8.
0u8.eq?(0u8) => true
5u8.eq?(7u8) => false
impl Eq<u16>
eq?
def eq?(self, other: Self) -> bool
Value equality for u16.
0u16.eq?(0u16) => true
5u16.eq?(7u16) => false
impl Eq<u32>
eq?
def eq?(self, other: Self) -> bool
Value equality for u32.
0u32.eq?(0u32) => true
5u32.eq?(7u32) => false
impl Eq<u64>
eq?
def eq?(self, other: Self) -> bool
Value equality for u64.
0u64.eq?(0u64) => true
5u64.eq?(7u64) => false
impl<T: Eq> Eq<Option<T>>
eq?
def eq?(self, other: Self) -> bool
Equal when both are None, or both Some of equal values.
Some(1i32).eq?(Some(1i32)) => true
Some(1i32).eq?(Some(2i32)) => false
Some(1i32).eq?(None) => false
listeq_all?
def _list_eq_all?<T: Eq>(a: List<T>, b: List<T>) -> bool
Element-wise; callers gate on equal length first. A counting loop, not per-element recursion: a list is wide and not deep, and comparing a long one must neither consume call-stack per element (a native stack overflow on the AOT tier) nor count against the == depth guard; only nesting (an element's own .eq?()) recurses.
impl<T: Eq> Eq<List<T>>
eq?
def eq?(self, other: Self) -> bool
Element-wise equality: same length, equal elements in order.
List.empty().append(1i32).eq?(List.empty().append(1i32)) => true
List.empty().append(1i32).eq?(List.empty().append(2i32)) => false
List.empty().append(1i32).eq?(List.empty()) => false