Skip to content

Optionals (Essential)

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 Optional 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?

An Optional is 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~;
    

🎁 Unwrapping an Optional

Note

Unwrapping an Optional is safe because it checks it has a result present before trying to use it.

After unwrapping, the variable then behaves like a normal variable, a non-Optional.

Checking if an Optional is empty

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

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_function() { /* ... */ }

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

Optionals in expressions produce Optionals

If you 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 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);
    }
}

Interfacing with C

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.