Nullability

All four Elements languages have the ability to specify the nullability of type references. Type references can be variables, fields, properties or method parameters. For brevity, we'll use the term "variables" throughout this topic to refer to all four kinds of references.

Nullable variables may either contain a valid value or they may not — in the latter case they are considered to be nil (in Oxygene and Swift parlance) or null (in C# parlance). Non-nullable variables must always contain a value and cannot be null or nil.

The different languages have different ways of expressing the nullability of a variable.

Oxygene and C#

In Oxygene and C#, the default nullability of a variable is determined by its type. For value types (structs, enums and simple numeric types), variables are assumed to be non-nullable by default, and always have a value. Reference types (i.e. classes or interfaces) are considered nullable by default, and may be nil.

For example:

var i: Int32; // non-nullable by default, initialized to 0
var b: Button; // nullable by default, will be nil
Int32 i; // non-nullable by default, initialized to 0
Button b; // nullable by default, will be null

Nullability of a type can be changed by explicitly amending the type name with a modifier. In Oxygene, value types can be made nullable with the nullable keyword, and reference types can be made non-nullable with the not nullable keyword combination. In C#, value types can be made nullable by appending ? to the typename, and reference types can be made non-nullable by appending !, respectively.

For example:

var i: nullable Int32; // nullable
var b: not nullable Button := new Button(); // not nullable
Int32? i; // nullable
Button! b = new Button() // not nullable

In the above example, the value type Int32 was modified to make the declared variable nullable, while the reference type Button (we're assuming Button is a class) was made non-nullable. As you will also notice, the non-nullable variable requires immediate initialization (in this case by assigning a newly created Button instance), because the default value of the type, nil or null, would be invalid for a non-nullable variable.

Note that type inference will always favor the type's default nullability. For example, in the code below, s will be inferred to be a regular nullable string, even though it's being assigned a decidedly non-null value.

var s := `Hello`;
var s = "Hello";

Swift

The Swift language behaves slightly different. Regardless of the underlying type, all types are treated as being non-nullable by default – even reference types.

For example:

var i: Int32 = 0; // non-nullable by default
var b: Button = Button(); // non-nullable by default

In addition, you'll also notice that Swift requires any non-nullable variable to be explicitly initialized with a default value. Where Oxygene and C# would assume a default value of 0 for the integer, Swift requires an initial value to be provided.

Swift has two ways to mark a variable as nullable, and that is either by appending a ! or a ? to the typename:

var i: Int32! // nullable
var b: Button! // nullable

var i2: Int32? // nullable
var b2: Button? // nullable

Conceptually, the nullable variables declared with ! and ? are the same. Both i and i2 are of type nullable integer and initially are nil; both b and b2 are of type nullable Button and also initially nil.

The difference lies in how the two sets of variables can be used in subsequent code.

The variables declared with ! are what Swift calls implicitly unwrapped nullables. That means that even though the variable may be nil, you can use it as you please, and – for example – directly call members on it or (in the case of the integer) use it in an arithmetic expression. If you try to call into a member of i or b and the actual variable is nil at runtime, a Null Reference Exception will be thrown. (This is how all nullable types behave in Oxygene and C#, meaning that all nullable types in those languages are implicitly unwrapped nullables.)

By contrast, the variables declared with ? do not allow direct access to the type they may (or may not) reference. All you can do with variables i2 and b2 is to compare them to nil, and to explicitly unwrap them in order to gain access to their content.

Note: While this syntax is not recommended for user code, the ¡ (inverted exclamation point) suffix can be used in Silver's Swift dialect to mark a type as being nullable only if it is a reference type – essentially giving it the same nullability behavior of the type name being used on its own in Oxygene or C#. This is mainly used by code generators to express this behavior where the kind of type is not known at generation time.

var i: Int32¡ // not nullable, since Int32 is a value type
var b: Button¡ // nullable, since Button is a reference type

Unwrapping them can be done in two ways:

Unwrapping a Nullable Inline

You can use the ! or ? operator on the variable to explicitly unwrap them in place before calling a member. For example:

let t1 = b2!.title
let t2 = b2?.title

Using ! will check if the b2 variable has a value. If it does, the title property on that Button instance will be called and returned into t1. If it does not, a Null Reference Exception will be thrown. Assuming the title property was declared as type String, then t1 will also be of type String, i.e. it will be assumed to be non-null.

When using ? instead, the variable will still be checked for a valid value, and if it has a valid value, that instance's 'title property will be called. However, if b2 is nil instead, the call to title will be bypassed, and t2 will be initialized to nil instead. As you might expect, this means t2 itself will be of type String? –in other words a nullable String. (In essence ? behaves similarly to the Colon Operator (:) in Oxygene and the similar "Elvis" operator (?.) in C#.)

Conditionally Unwrapping a Nullable

If you are planning to do more extensive calls on a nullable variable, it often makes sense to conditionally unwrap it into a new non-nullable variable with Swift's if let construct:

if let b3 = b2 {
    let t3 = b3.title
}

The if let construct checks if the right hand of the assignment contains a value or is nil. If it contains a value, that value is assigned to a new, non-nullable variable (in this case b3) for the scope of the following block, and that block is executed. If the right hand side of the assignment is nil, the whole block is skipped.

Note how inside the block, members of b3 can be called without the need for ! or ?.

Comparing Swift Nullables to Oxygene and C#

Conceptually, nullable Variables in C# and Oxygene behave equivalent to implicitly unwrapped nullables in Swift declared with !. They can contain nil, but you can still access all their members directly – at the risk of a Null Reference Exception (NRE) occurring, if you don't make sure a variable is not nil.

Oxygene and C# have no equivalent for Swift's more strict wrapped nullable types declared with ?.

A second crucial difference to keep in mind, especially when working with both C# and Swift, is that while Swift uses both ?and ! to mark a variable as nullable, ! has in fact the opposite meaning in RemObjects C#, marking an otherwise nullable reference type as *not nullable.

Nullability of Types Compared:

Type Value Type Reference Type
Oxygene Non-Nullable Int32 not nullable String
Oxygene Unwrapped Nullable nullable Int32 String
Oxygene Wrapped Nullable N/A nullable String
C# Non-Nullable Int32 String!
C# Unwrapped Nullable Int32? String
C# Wrapped Nullable N/A String?
Swift Non-Nullable Int32 String
Swift Unwrapped Nullable Int32! String!
Swift Wrapped Nullable Int32? String?

The Oxygene Colon (:) Operator

Oxygene pioneered the colon operator to allow safe member calls on potentially nil variables. Both C# (in RemObjects' dialect and in the upcoming C# 6.0) and Swift take on the same concept by combining the ? operator with a subsequent dot (.) for member access.

For example:

var b: Button;
var i = b:title:length; // i is nullable Int32
Button b;
var s = b?.title?.length; // i is Int32?
let b: Button?
var i = b?.title?.length // i is Int32?

Version Notes

  • The advanced nullability features discussed below are new in Version 8.2.

Exposing Wrapped Nullables to Swift from Oxygene and C#

For the purpose of being able to create APIs in Oxygene and C# that play well with Swift, the languages provide a way to mark variables as wrapped, even though the languages themselves do not use the concept. If a class type is explicitly marked as "nullable", it will appear as a wrapped nullable when referred to from Swift:

var b: nullable Button; // already nullable by default, "nullable" makes it wrapped to Swift
Button? b; // already nullable by default, "?" makes it wrapped to Swift

Optional Nullability Warnings in C# and Oxygene

As discussed above, all nullables in Oxygene and C# are considered unwrapped, meaning they can be accessed without explicit unwrapping, and they can be assigned to variables that are declared non-nullable – at the risk of generating an NRE when an actual null value is encountered. This is of course par for the course for Oxygene and C# developers.

When working in Oxygene and C# with APIs designed for Swift, that is, APIs that make explicit use of nullable and non-nullable type references a lot, the compiler will optionally emit warnings when assigning potential null values to variables that are declared nullable. This can help you spot potential NREs at compile time rather than when debugging.

Because in many cases these warnings are noisy and can provide false positives, they can be turned off in Project Settings via the "Warn on Implicit Not-Nullable Casts" option. When turned on (the default), warnings will be generated in cases such as the one below:

var b: Button! := ...;
var c: Button;
b := c; // warning here, as c might be nil
Button! b = ...;
Button c;
b = c; // warning here, as c might be null

There are two ways to "fix" these warnings and let the compiler know that the assignment is safe, or that the developer is aware of the potential risk of an NRE. The first is to enclose the assignment in code that ensures the variable is not null in a way that the compiler can detect as part of code flow analysis, for example with an if statement:

if c ≠ nil then
  b := c; // no warning here, we know c is assigned
if (c != null)
    b = c; // no warning here, we know c is assigned

The second option is to explicitly "cast" the value to a non-null version. This can be done either by using type cast syntax with a nullable type name, or by using a shorthand syntax provided by the language. In C#, the shorthand syntax uses the ! operator, similar to Swift. In Oxygene, the as not nullable keyword combination, without an explicit type name, can be used:

b := c as not nullable Button; // type-cast to non-nullable type
b := c as not nullable;       // convenience shorthand syntax
b = c as Button!; // type-cast to non-nullable type
b = c!;          // convenience shorthand syntax

Exposing Wrapped nullable to Swift from C# and Oxygene

When writing APIs in Oxygene and C# that will (also) be consumed from Swift, it is sometimes (or rather, often) desirable to expose nullable types as wrapped nullables on the Swift side.

Even though Oxygene and C# have no concept of wrapped nullables themselves, reference types (and only reference types) that are explicitly (and, in a sense, redundantly) marked as nullable in Oxygene and C# will be exported as wrapped nullable when seen from Swift. For example, a type of nullable String (Oxygene) or String? (C#) will appear in Swift as wrapped nullable String?, while a regular nullable String (Oxygene or C#) will appear to Swift as a unwrapped nullable String!.

There is currently no way to expose nullable value types as wrapped nullables from Oxygene and C#.

Nullability Information in Cocoa SDKs

Starting with the 2015 releases of Apple's Cocoa SDKs, their Objective-C APIs have been annotated with nullability information, marking parameters as nullable or non-nullable (usually wrapped, in the latter case), where appropriate. This applies to iOS 9.0, watchOS 2.0, tvOS 9.0 and OS X 10.11 and later.

Elements has full support for these annotations in Objective-C, and carries the nullability of parameters, method results and properties over into the APIs seen by Oxygene, C# and Silver. Because of this, many APIs are much more expressive and precise when building against the newer SDKs. This also means that your own code consuming these standard Cocoa APIs might need adjustment for more explicit handling of nullability, for example when overriding platform methods, or when dealing with return types.

Nullability information is available in the new SDKs' .FX files, and also when importing custom third party libraries with FXGen.