← All Examples Types
Error Handling
JAPL has no exceptions. Errors are values, represented as algebraic types. Pattern matching forces you to handle both success and failure at every call site.
Source Code
type MyResult =
| MyOk(Int)
| MyErr(String)
fn divide(a: Int, b: Int) -> MyResult {
if b == 0 { MyErr("division by zero") }
else { MyOk(a / b) }
}
fn main() {
match divide(10, 2) {
MyOk(n) => println("10/2 = " <> show(n))
MyErr(e) => println("Error: " <> e)
}
match divide(10, 0) {
MyOk(n) => println("10/0 = " <> show(n))
MyErr(e) => println("Error: " <> e)
}
} What This Demonstrates
- Errors as values --
MyResultis a plain algebraic type. There is no special exception mechanism. - Forced handling -- You cannot access the success value without matching against the error case too. The compiler enforces exhaustive matching.
- Descriptive errors -- The
MyErrvariant carries aStringmessage explaining what went wrong. - No surprise panics -- Division by zero returns an error value instead of crashing the program.
Line-by-Line Breakdown
type MyResult = | MyOk(Int) | MyErr(String)- A result type with two variants:
MyOkwraps a successfulIntvalue;MyErrwraps an error message string. fn divide(a: Int, b: Int) -> MyResult- A safe division function. Instead of crashing on division by zero, it returns a
MyErrvalue. if b == 0 { MyErr("division by zero") }- Guards against the error case and returns a descriptive error value.
else { MyOk(a / b) }- The happy path wraps the result in
MyOk. match divide(10, 0) { MyOk(n) => ... MyErr(e) => ... }- The caller must handle both cases. Trying to use the value without matching would be a compile error.
Try Modifying
- Make
MyResultgeneric:type Result[T] = | Ok(T) | Err(String)so it works with any value type. - Chain multiple operations: write a
thenfunction that applies a function only if the result isMyOk. - Add more error variants:
| Overflow | Underflowfor a numeric library. - Implement
unwrap_or(result, default)that returns the value or a fallback.