hanki

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