Skip to content

Essential Error Handling

In this section we will go over the essential information about Optionals and safe methods for working with them, for example if (catch optional_value) and the Rethrow operator !.

In the advanced section there are other nice to have features. Like an alternative to safely unwrap a result from an Optionals using if (try optional_value) and an unsafe method to force unwrap !! a result from an Optional, return default values for optionals ?? if they are empty and other more specialised concepts.

What is an Optional?

Optionals are a safer alternative to returning -1 or null from a function, when a valid value can’t be returned. An Optional has either a result or is empty. When an Optional is empty it has an Excuse explaining what happened.

  • For example trying to open a missing file returns the Excuse of IoError.FILE_NOT_FOUND.
  • Optionals are declared by adding ! after the type.
  • An Excuse is of the type anyfault.
int! a = 1; // Set the Optional to a result

The Optional Excuse is set with ? after the value.

// Set the Optional to empty with a specific Excuse.
int! b = IoError.FILE_NOT_FOUND?;

🎁 Unwrapping an Optional

Checking if an Optional is empty

import std::io;
fn void! test()
{
// Return an Excuse by adding '?' after the fault.
return IoError.FILE_NOT_FOUND?;
}
fn void main(String[] args)
{
// If the Optional is empty, assign the
// Excuse to a variable:
if (catch excuse = test())
{
io::printfn("test() gave an Excuse: %s", excuse);
}
}

Automatically unwrapping an Optional result

If we escape the current scope from an if (catch my_var) using a return, break, continue or Rethrow !, then the variable is automatically unwrapped to a non-Optional:

fn void! test()
{
int! foo = unreliable_function();
if (catch excuse = foo)
{
// Return the excuse with `?` operator
return excuse?;
}
// Because the compiler knows 'foo' cannot
// be empty here, it is unwrapped to non-Optional
// 'int foo' in this scope:
io::printfn("foo: %s", foo); // 7
}

Using the Rethrow operator ! to unwrap an Optional value

  • The Rethrow operator ! will return from the function with the Excuse if the Optional result is empty.
  • The resulting value will be unwrapped to a non-Optional.
import std::io;
// Function returning an Optional
fn int! maybe_func() { /* ... */ }
fn void! main()
{
// ❌ This will be a compile error
// maybe_function() returns an Optional
// and 'bar' is not declared Optional:
// int bar = maybe_function();
int bar = maybe_function()!;
// βœ… The above is equivalent to:
// int! temp = maybe_function();
// if (catch excuse = temp) return excuse?
// Now temp is unwrapped to a non-Optional
int bar = temp; // βœ… This is OK
}

⚠️ Optionals affect types and control flow

Optionals in expressions produce Optionals

Use an Optional anywhere in an expression the resulting expression will be an Optional too.

import std::io;
fn void! main(String[] args)
{
// Returns Optional with result of type `int` or an Excuse
int! first_optional = 7;
// This is Optional too:
int! second_optional = first_optional + 1;
}

Optionals affect function return types

import std::io;
fn int test(int input)
{
io::printn("test(): inside function body");
return input;
}
fn void! main(String[] args)
{
int! optional_argument = 7;
// `optional_argument` makes returned `returned_optional`
// Optional too:
int! returned_optional = test(optional_argument);
}

Functions conditionally run when called with Optional arguments

When calling a function with an Optionals as arguments, the result will be the first Excuse found looking left-to-right. The function is only executed if all Optional arguments have a result.

import std::io;
fn int test(int input, int input2)
{
io::printn("test(): inside function body");
return input;
}
fn void! main(String[] args)
{
int! first_optional = IoError.FILE_NOT_FOUND?;
int! second_optional = 7;
// Return first excuse we find
int! third_optional = test(first_optional, second_optional);
if (catch excuse = third_optional)
{
// excuse == IoError.FILE_NOT_FOUND
io::printfn("third_optional's Excuse: %s", excuse);
}
}

Interfacing with C

For C the interface to C3:

  • The Excuse in the Optional of type anyfault is returned as the regular return.
  • The result in the Optional is passed by reference.

For example:

// C3 code:
fn int! get_value();
// Corresponding C code:
c3fault_t get_value(int *value_ref);

The c3fault_t is guaranteed to be a pointer sized value.