16. Error handling
Jik uses throwing functions and structured handling at the call site. The current model is centered around four language constructs:
throws funcmarks a function as one which may failfail(String[, int])marks failure inside a throwing functiontry ... except ... endhandles a failure locallymust f(...)requires a throwing call to succeed
Errors are therefore handled as control flow.
16.1 Declaring a throwing function
A function which may fail must be declared with throws func:
throws func div_safe(x, y):
if y == 0:
fail("division by 0")
end
return x / y
end
Inside such a function, fail(...) marks the operation as failed. fail(msg) uses the default error code 1, while fail(msg, code) lets you set an explicit integer error code. fail(...) also terminates the current control-flow path. Any statement that follows fail(...) in the same block is unreachable and is a compile error.
16.2 Handling failures with try / except
A throwing call can be wrapped in a try block:
try x := div_safe(3, 0):
print("ok: ", x)
except:
print("error")
end
If the call succeeds, execution continues in the try body. If it fails, the except branch runs instead.
The variable introduced in try x := ... is only valid in the try body. Referring to it inside except or after the entire try/except/end construct is a compile error.
If the throwing function returns nothing, we simply write:
try foo():
print("OK")
except:
print("err")
end
16.3 Propagating failures
Within a throws func, try can pass a failure to the caller instead of handling it locally. It can be used with either a declaration or a call that does not return a value:
throws func half_of_safe_value():
x := try div_safe(10, 2)
return x / 2
end
throws func save_record(record):
try write_record(record)
end
The value declared by x := try ... is in the enclosing scope. In either form, if the call fails, the function returns through its error path before any following statement runs. The propagation form has no trailing colon; a colon starts a local try ... except ... end handler.
16.4 Requiring success with must
When a failure does not need to be handled separately, use must:
x := must div_safe(10, 2)
must calls a throwing function and panics if the call results in an error.
The postfix form call()! has the same behavior and is convenient within expressions:
x := div_safe(10, 2)!
print(div_safe(10, 2)! == 5)
! applies only to function calls. Use try ... except ... end when failure needs local recovery.
16.5 Inspecting the current error
Inside an except block, the current error can be inspected with:
error_msg([Region]) -> Stringerror_code() -> int
error_msg() may be called with no argument, in which case the local region _ is used.
Example:
try result := div_safe(10, 0):
println("result = ", result)
except:
println("failed: ", error_msg())
println("code = ", error_code())
end
16.6 Example
throws func div_safe(x, y):
if y == 0:
fail("division by 0")
end
return x / y
end
func main():
try result := div_safe(10, 0):
println("result = ", result)
except:
println("division failed")
end
ok := must div_safe(10, 2)
println(ok)
end
---