hanki

result

stdlib/result.hk: Result<T, E> and its helpers.

Result<T, E> is the canonical "success or typed failure" sum from HANKI.md §4. It has the error payload Option drops, and is the value a pure function returns in place of throwing: computed and logical failure in pure code is a value and no effect (HANKI.md §7). throws is for world-coupled failure in the imperative shell (io errors, actor SendFailed).

Pure Hanki: the same sum-type and variant-ctor machinery as Option, with map, map_err ride on the higher-order call lowering so they can invoke their fn-typed callbacks.

Methods land on impl<T, E> Result<T, E> ... end; call as r.unwrap_or(default), r.map(|v| ...), r.map_err(|e| ...).

Result

type Result<T, E>
  Ok(T)
  Err(E)
end

impl<T: Eq, E: Eq> Eq<Result<T, E>>

Hand-written (not @derive) so it sits in the baked stdlib prefix: stdlib bodies compare Result (e.g. to_string() == Ok(..)), and a stale-blob chunked lower must resolve Eq<Result<T, E>> from the prefix and not from an impl synthesised after the user items. Mirrors @derive(Eq) (structural, under T: Eq, E: Eq); the build_stdlib_bytecode assert checks its self-containment.

eq?

def eq?(self, other: Self) -> bool

Equal when both are the same variant holding equal values.

Ok(1i32).eq?(Err(2i32)) => false

impl<T, E> Result<T, E>

unwrap_or

def unwrap_or(self, default: T) -> T

Returns the wrapped value, or default when the result is Err.

ok: Result<i32, string> = Ok(7i32)
ok.unwrap_or(0i32)  => 7i32
bad: Result<i32, string> = Err("nope")
bad.unwrap_or(9i32) => 9i32

or_crash!

def or_crash!(self, msg: string) -> T [Crash]

Returns the wrapped value, or crash!es with msg when the result is Err, killing the current actor, and at the program root exiting 252 after printing crash: <msg> (HANKI.md §15). For a script that has nothing useful to do with a failure and would otherwise write the same four-line match at every call: fs.read!(p).or_crash!("build: cannot read #{p}").

The message is required and is not synthesised from the error, because what a reader needs at the crash is which path failed, and only the caller knows that.

This is the one partial method on Result, and the one stdlib API that takes [Crash]. Both are intended and neither is a precedent: every other method here is total, and a caller who wants to handle the failure should still match, unwrap_or, or or_else. [Crash] is viral (H0601: every caller up the chain must declare it) and no pure def can call it at all (H0606), and this is unusable from library code that means to stay pure, and free in a script whose main! is already an action. That asymmetry is the design: it puts the shortcut where crashing is the right answer, and leaves it out of everywhere else.

ok: Result<i32, string> = Ok(7i32)
ok.or_crash!("unreachable — the result is Ok") => 7i32

map

def map(self, f: (T) -> U) -> Result<U, E>

Applies f to the success value, or propagates Err untouched.

ok: Result<i32, string> = Ok(3i32)
ok.map(|x| x * 2i32) => Ok(6i32)

map!

def map!(self, f: (T) -> U [e]) -> Result<U, E> [e]

map with an effectful transform. f runs only on Ok, and an Err propagates without performing anything.

ok: Result<i32, string> = Ok(3i32)
ok.map!(|x: i32| x * 2i32) => Ok(6i32)

map_err

def map_err(self, f: (E) -> F) -> Result<T, F>

Applies f to the error value, or propagates Ok untouched.

bad: Result<i32, string> = Err("x")
bad.map_err(|e| "#{e}!") => Err("x!")

map_err!

def map_err!(self, f: (E) -> F [e]) -> Result<T, F> [e]

map_err with an effectful transform, the one that lets a failure be logged or annotated from the world on its way up. f runs only on Err.

bad: Result<i32, string> = Err("x")
bad.map_err!(|e: string| "#{e}!") => Err("x!")

and_then

def and_then(self, f: (T) -> Result<U, E>) -> Result<U, E>

Chains a fallible step: it applies f, which itself yields a Result, to the success value, or propagates Err untouched. The flattening counterpart of map, and a chain of fallible steps therefore nests no Results.

ok: Result<i32, string> = Ok(3i32)
ok.and_then(|x| Ok(x * 2i32)) => Ok(6i32)

and_then!

def and_then!(self, f: (T) -> Result<U, E> [e]) -> Result<U, E> [e]

and_then with an effectful step, the effectful pipeline, where each stage both touches the world and may fail. f runs only on Ok.

ok: Result<i32, string> = Ok(3i32)
ok.and_then!(|x: i32| Ok(x * 2i32)) => Ok(6i32)

or_else

def or_else(self, f: (E) -> Result<T, F>) -> Result<T, F>

Recovers from a failure: it applies f, which itself yields a Result, to the error value, or propagates Ok untouched. The fallback counterpart of and_then; f may change the error type.

bad: Result<i32, string> = Err("xy")
bad.or_else(|e| Err(e.length)) => Err(2)

or_else!

def or_else!(self, f: (E) -> Result<T, F> [e]) -> Result<T, F> [e]

or_else with an effectful recovery: retry, fall back to another source, or report. Lazy like the pure form, and an Ok performs nothing.

bad: Result<i32, string> = Err("xy")
bad.or_else!(|e: string| Err(e.length)) => Err(2)