Optionals and Error Handling
Optionals in C3 work differently from other languages:
- It is similar to a “Result” type in capabilities.
- It is not quite a type: it can be used for variable and return values - but not for parameters or struct member types.
- It “cascades” the optional-ness of an expression, somewhat reminiscent of the “flatmap” operation.
- A function called with an Optional is only invoked if it has an actual value.
What is an Optional?
In C3 it is either:
- A variable that works as a union between a
fault
value and an actual value, we call this latter the “result”. - A return value that is either a
fault
or a result.
We can construct an Optional by adding the !
suffix to a type:
It is not possible to create an Optional of an Optional (so for example int!!
is never valid).
Using a variable or return with an Optional type yields an optional:
Similar to basic operations it’s possible to use an Optional value as a call parameter. The return value then becomes Optional
Optional execution
The most important feature of Optionals is how it will only optionally execute operations. Given a call with the arguments a0, a1, … the call will only be invoked if all of the arguments evaluate to real values.
We can think of the above example int! x = baz(a, b)
as the following:
- Evaluate the first argument.
- If it is a
fault
then we’re done, setx
to this fault. - Evaluate the second argument.
- If it is a
fault
then we’re done, setx
to this fault. - Execute
baz
with the result values from the arguments.
Optional execution allows us to avoid dealing with intermediary errors, we can simply collect them together:
int! x = foo_return_optional(other_return_optional(optional_value))
Optional unwrapping
It’s not possible to assign an Optional to a non-optional type:
To assign it we have two options, if-try
and implicit unwrap.
If-try
If-try tests an Optional and executes the “then” part if the value is a result.
There are abbreviated variants of if-try
:
It is possible to add conditions to an if-try
but they must be joined with &&
“or” (i.e. ||
) is not allowed:
If-catch
If-catch works the other way and only executes if the Optional is a fault:
Just like for if-try there are abbreviated variants:
It is possible to catch multiple errors by grouping them with ,
:
Implicit unwrapping with if-catch.
If an if-catch
returns or jumps out of the current scope in some way, then
the variable becomes implicit unwrapped to its result type in that scope:
Getting the fault without unwrapping
If-catch is not necessary in order to get the underlying fault from any Optional. Instead the macro @catch
may be used.
If-catch switching
If-catch can also immediately switch on the fault value:
The above being equivalent to:
Testing for a result without unwrapping
The @ok
macro will return true
is an Optional is a result and false
it is a fault. Functionally it is equivalent to !@catch
fault
and anyfault
Faults are defined similar to simple enums:
The union of all of such types is anyfault
:
Setting the result
and the fault
To set the result
of an Optional, use regular assignment, and
to set the fault
?
suffix operator.
Rethrow, or-else and force unwrap
Three helper operators are provided for working with Optionals:
rethrow !
, or-else ??
and force unwrap !!
.
Rethrow
Sometimes the optional fault needs to be propagated upwards, here is an example:
To simplify this the rethrow operator !
can be used:
Because the rethrow operator automatically returns on a fault, the return value
turns into its result. In the above example the type of foo_may_error()!
becomes int
:
Or-else
Sometimes we have this situation:
The or-else operator ??
works similar to ?:
allowing you to do this in a single expression:
Force unwrap
Sometimes a fault
is completely unexpected, and we want to assert if
it happens:
The force unwrap operator !!
allows us to express this similar to rethrow and or-else:
No void! variables
The void!
type has no possible representation as a variable, and may
only be a return type. To store the result of a void!
function,
one can use the @catch
macro to convert the result to
an anyfault
:
Examples
Basic usage with immediate error handling
We can also execute just in case of success:
Composability of calls
Returning a fault
Returning a fault looks like a normal return but with the ?
Calling a function automatically returning any optional result
The !
suffix will create an implicit return on a fault.
Force unwrapping to panic on fault
The !!
will issue a panic if there is a fault.
Catching faults to implicitly unwrap
Catching faults and then exiting the scope will implicitly unwrap the variable:
Only run if there is no fault
Catching and switch on fault
Default values using or-else
Get the fault from an optional without if-catch
Test if something has a value without if-try
Some common techniques
Here follows some common techniques using optional values.
Catch and return another error
In this case we don’t want to return the underlying fault, but instead return out own replacement error.
Using void! as a boolean
A common pattern in C is to use a boolean result to indicate success. void!
can be used
in a similar way:
Interfacing with C
For C the interface to C3, the fault is returned as the regular return while the result is passed by reference:
C3 code:
Corresponding C code:
The c3fault_t
is guaranteed to be a pointer sized value.