Sifr Error Handling: Result Types and Custom Errors
Learn how Sifr replaces exceptions with Result[T, E] return types, compiler-enforced error handling, and custom Error classes — no runtime surprises.
Sifr eliminates runtime exceptions by making errors part of a function’s return type. When a function can fail, it declares Result[T, E] as its return type. The compiler then refuses to compile any call site that does not handle the error — you cannot accidentally ignore a failure the way you can with an uncaught exception in Python.
Result[T, E] is a built-in union that represents either a successful value (Ok(T)) or a failure (Err(E)). You never construct Ok or Err directly in Sifr source code — the compiler handles the wrapping for you.
A plain return value is automatically wrapped in Ok.
A raise statement inside a Result-returning function is mapped to Err.
class ParseError(Error): message: strdef parse_age(input: str) -> Result[int, ParseError]: if input == "": raise ParseError("empty input") # maps to Err(...) return int(input) # auto-wrapped in Ok(...)
raise inside a Result-returning function does not unwind the call stack. It is syntactic sugar for constructing and returning an Err variant. No exception propagates.
Define your own error types by subclassing Error. Add typed fields to carry diagnostic information — the compiler treats them as plain structs.
class ValidationError(Error): message: strdef validate_range(x: int, lo: int, hi: int) -> Result[int, ValidationError]: if x < lo: raise ValidationError(f"value out of range: {x}") if x > hi: raise ValidationError(f"value out of range: {x}") return x
You can define as many error types as you need. Each one is an independent type, so the compiler can distinguish them in try/except branches.
Use try/except to consume a Result-returning function. Inside the try block, the return value is automatically unwrapped to its success type T. The except clause receives the typed error value.
def main(): try: age: int = parse_age("25") # auto-unwrapped to int print(f"age: {age}") except ParseError as e: print(e.message) # compiler error if you forget to handle ParseError ^
If you call a Result-returning function without a try/except, the compiler emits an error. You must either handle the error or explicitly discard it with _ = ....
If you intentionally do not need the result of a fallible call, assign it to _. This signals to the compiler that the discard is deliberate, not accidental.
Use assert to express invariants that must hold at a specific point in your program. An assertion failure is a programmer error, not a recoverable runtime condition — it immediately aborts the program with a diagnostic message.
x: int = 42assert x > 0print("all assertions passed")
Reserve assert for internal correctness checks. Use Result and custom error types for anything that could fail due to external input or environment conditions.