Nullable types

If you see a type with a question mark at the end – such as boolean? – then you are looking at a nullable type. The nullable version of a type adds the ability for a variable to have the value null. Variables of nullable types may be created using explicit type annotations, by evolution, or from other steps such as User steps. For instance, Numeric input items with the Allow Empty Input option enabled create values of type number? (which can be pronounced "nullable number").

Only variables with nullable types can have the value null.

// Example 1: create a nullable variable by evolution.
let x = null;
set x = 1;

// Example 2: create a nullable variable by explicit type annotation.
let y: number? = 1;
set y = null;

// Example 3: create a non-nullable variable.
let z = 1;
set z = null; // ERROR: Expected a 'number' but the term has type 'null'.

Refining nullable values

A non-nullable type is always compatible with its nullable counterpart, but a nullable type is not compatible with its non-nullable counterpart. Many common operations in FlowScript are only defined for non-null values. Therefore, nullable values must often be refined before you can work with them.

One such example is the multiplication operator, which works with numbers but not nullable numbers:

// Example: we imagine that we have received an non-local variable "totalPrice"
// which is a nullable number. We try to use the variable in a calculation.
// This gives a design-time error.

// ERROR: Expected a 'number' but the term has type 'number?'
let discountedPrice = totalPrice * 0.8;

To calculate the discounted price, we must refine the totalPrice variable. In other words, we must decide what the discounted price will be if the total price is null. We do this using a null guard expression:

let discountedPrice = 0;

if totalPrice != null {
   // Because this code is only run when totalPrice is not null,
   // there is no error here.
   set discountedPrice = totalPrice * 0.8;
}

In the example above, FlowScript is analyzing the program to deduce that totalPrice can be safely multiplied with another number inside the if-branch. The act of using a null guard expression on a variable temporarily changes its type in the parts of the program guarded by the expression.

Advanced: More on null guards

What constitutes a null guard expression? The equation, inequation and type narrowing operators can all be used to refine a value:

if x != null {
    // x is refined to not-null here
}

if not (x == null) {
    // x is refined to be not-null here
}

if not (x is null) {
    // x is refined to be not-null here
}

In each of the examples above, the code inside the if block is only executed if the variable is not null. FlowScript keeps track of this when type-checking the program.

Null guards can also be used in case expressions, or in conjunction with the return or error statements.

if totalPrice == null:
    return 0;

// We would not reach this line of code
// if totalPrice was null, so we are allowed to use it in multiplication.
return totalPrice * 0.8;

Several null guards may combined using the and keyword as expected:

if x != null and y != null {
    return x * y;
}

Primitive

Last updated