Optionals and sentinels

Optionals and sentinels

Three states

Option<T> holds a value, None, or an error carrying a message. All three are the same type.

[public] fn is_value() => bool
[public] fn is_none() => bool
[public] fn is_error() => bool
[public] fn unwrap() => T

Error("message") builds the error state. ? propagates a None or an error to the caller; ? else { } handles both locally.

unwrap() on a non-value terminates the program after printing a failure backtrace. It does not return a default and it is not recoverable, so guard with is_value() or use ?.

What None means, per call

Call None means
Array.first/last/pop the array is empty
Array.find nothing matched
Map.get, IndexMap.get, JsonObject.get the key is absent
fs.read_file, fs.read_bytes the file could not be read
FileHandle.read* end of file, an error, or the handle is closed
File.open could not open
io.read_line a read error, not distinguishable from end of input
crypto.*_decrypt authentication failed
crypto.from_hex/from_base64 the input was not valid
sockets.Request.send/send_http transport error or an empty reply
sockets.WsClient.recv the connection closed or errored
threads.Channel.recv the channel is closed and drained
threads.Channel.try_recv empty or closed, indistinguishable
threads.Future.await the future was cancelled
process.spawn/spawn_in/run the process could not start
process.Process.read_line end of output
process.Process.try_exit_code the child is still running
random.choice the array is empty
xml.Element.get_attribute the attribute is absent

The sentinels

These do not use an optional, so nothing forces a check. They are the most common source of quiet bugs in Peko code.

Call Sentinel
string.index_of -1
Array.index_of_where, search, linear_search, binary_search -1
process.Process.write, write_line a negative number
sockets.send_request, send_request_tls, Request.send_raw a string starting with Error:
string.to_number 0 for non-numeric text
random.next_int returns min when max <= min
fs.list_dir an empty array for an unreadable directory
pekoui env.get an empty string when unset

Treat a -1 or a negative return as the failure case explicitly:

let written = child.write_line(payload)
if written < 0 {
    return Error("could not write to the child process")
}