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.

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 io::FILE_NOT_FOUND.
  • Optionals are declared by adding ? after the type.
  • An Excuse is of type fault.
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 = io::FILE_NOT_FOUND?;
import std::io;
fn void? test()
{
// Return an Excuse by adding '?' after the fault.
return io::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

Section titled “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

Section titled “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? test()
{
// ❌ 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

Section titled “⚠️ Optionals affect types and control flow”

Optionals in expressions produce Optionals

Section titled “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;
}
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

Section titled “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 = io::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 == io::FILE_NOT_FOUND
io::printfn("third_optional's Excuse: %s", excuse);
}
}

For C the interface to C3:

  • The Excuse in the Optional of type fault 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.