nil in ARMA 3 SQF

Most scripting languages have one single built in value that represents the concept of "nothing", and they usually call it null. ARMA 3 SQF—not happy to just be one of the crowd—has many different nil and null types, and has special rules for evaluating code involving nil. This isn't a necessarily a failing of SQF—there are good reasons for having both nil and null—but it does steepen the learning curve.

So, if you've ever wondered why any, scalar or [0x2917924] show up, or why you don't always get an error when scripts go bad, read on.

Variables and nil

When you access a variable that doesn't exist, a nil is returned:

[_variableThatDoesNotExist]; // Returns an array containing a single nil.

As well as returning a nil instance in an array, the above code will also cause an Undefined variable in expression error if the script is running in a scheduled environment. In an unscheduled environment, no error is returned. You can check this by running the following in the (unscheduled) Debug console window:

comment "No error is displayed, and no hint is shown.";
hint variableThatDoesNotExist;

If you run the same code in a scheduled environment—created by using spawn—an error is displayed:

comment "Displays an undefined variable error.";
[] spawn { hint variableThatDoesNotExist; }

In both cases, an undefined variable does not stop code from running, even when the undefined variable error is shown. For example, the following code will always result in a dead player:

hint variableThatDoesNotExist;
player setDamage 1;

You can also create nil instances directly:

hint [1, nil, 3];

Assigning a nil to a global variable using the = statement is a special case, and removes the variable from the namespace. Using setVariable with a nil value will also remove the variable from the namespace. You can check this by using allVariables on the namespace:

someVariable = 1;
someVariable = nil;
hint str (allVariables missionNamespace find "someVariable");

In contrast, assigning a nil to a private variable name does not remove the variable. If a private variable is set to nil in an inner scope, the inner variable continues to exist (with nil as the value) and the variables in any outer scopes continue to be shadowed:

_a = "brown";

call {
    // Calling creates a new scope.
    private "_a";

    _a = "fox";
    _a = nil;

    hint _a; // _a is nil here, not "brown".
}

The other way to verify that nil doesn't remove private variables is by storing a specific type of nil—which is discussed below—and then printing it.

Evaluation with nil

nil instances are also treated as a special case when evaluating operators. If the left or right-hand argument of an operator is a nil, the operator is not executed. Instead, the result of the operator is simply just another nil:

1 + nil // Returns nil, but does not raise any errors.

If the operator has a side effect—such as hint, which has the side effect of displaying a message on the screen—the side effect is also inhibited. When a nil is passed to hint (or when you hint an expression that evaluates to nil), nothing happens at all:

hint _doesNotExist; // Has no effect, may raise an error.
hint nil; // Has no effect, never raises an error.

Since SQF expressions are almost entirely operators, a nil appearing in an expression will usually cause the value of the entire expression to be nil:

// Prints nothing, returns nil:
if (true) then { hint "Reached"; } else nil;

This is why errors involving nil often don't result in any error message. Accessing an undefined variable in a non-scheduled environment—for example—won't raise an error. If the undefined variable is then used in a larger expression, the expression will also evaluate to nil without raising any errors.

isNil

The nil evaluation rules cause a bit of an issue for isNil. The isNil operator, if passed a nil on the right-hand-side, would not be executed and would instead evaluate to a nil:

isNil _nilOrUndefinedVariable // Always returns nil, not true or false.
isNil nil // Also returns nil rather than true.
isNil (1 + nil) // Again, returns nil.

Instead, for global and local variables, the name of the variable must be passed in as a string:

isNil "_nilOrUndefinedVariable" // Returns true if the variable is undefined or nil.

For everything else, the expression to be tested can be passed to isNil as a code block:

isNil { nil } // Returns true.
isNil { (1 + nil) } // Return true.

The code block is evaluated as if it was passed to the unary version of call—any side effects are not suppressed.

The isNull operator doesn't need to use strings or code blocks. The null values—objNull and configNull for example—are passed into operators like any other value.

Displaying nil

One of the few parts of SQF where nil has no special treatment is when it is used in an array:

hint str [nil]; // Hints "[any]"
hint str [player getVariable "undefinedVariable"]; // Hints "[<null>]", which is also a nil.

This is often used to print out nil values for debugging. It also reveals that—inside the SQF engine—nil can have many different types.

There are Many nils

When the SQF engine evaluates a unary or binary operator with nil on the left or right hand side—or on both sides—the operator is not actually executed. However, the arguments types are still checked, and an error will be raised if the nil arguments do not have the required types. A type error will also halt the evaluation of the script.

For example, if the variable stringTypeNil contains a string type nil, the following code will raise an error and won't run to completion:

someGlobal = [1 + stringTypeNil];

This code, however, works without error:

someGlobal = ["a" + stringTypeNil];

If the types are correct on an operator expression involving nil, the result of the operator is a nil with the return type of the operator. This can be abused to create nil values of a specific type:

stringTypeNil = name nil;
numberTypeNil = abs nil;
unitTypeNil = leader nil;

The nil returned from the nil operator, and the nil returned from an undefined variable access, is a nil whose type is the wildcard any type. When placed inside an array and converted to a string, it is either displayed as "nil", or in some cases, "<null>".

A few examples of nil descriptive names are:

Type Printed Example Code
ARRAY array getPos nil
BOOL bool local nil
CONFIG 0x2917a70 nil >> nil
SCALAR scalar NaN abs nil
STRING string str nil
GROUP 0x2917cac group nil
OBJECT 0x2917924 leader nil

Note that the numeric descriptions (such as 0x2917924 for objects) are quite likely to be memory addresses in the ARMA 3 process, and they may change between versions (or even runs) of ARMA 3.