Michele Volpato

Michele Volpato

Swift Optional vs Dart sound null safety

Dart

Recently the Dart team announced sound null safety. Dart was already a type-safe language, and now it is even better.

Sound null safety means that if a variable is not declared as nullable, then, whenever you access that variable, you will never find null, for its entire lifetime. This should remind you of Swift Optional.

Dart sound null safety and Swift Optional have a lot in common, but they are implemented in different ways. In Swift, Optional is a generic type, which means that, when we write var x: String?, what we mean is var x: Optional<String>: x is a variable that can contain a String or nil.

The Dart team took a different approach.

When you write String? x, the meaning is the same, x is a variable that can contain a String or null, but String? is a union type, it describes a value that can be one of several types.

In Dart, String is called a non-nullable type, while String? is a nullable type. In the rest of the article, we are going to refer to nullable types in Dart and to Optional in Swift as optionals, or optional types.

The two implementations are different, but they have a lot in common.

Optional binding

In Swift you can bind the value of an optional to a new variable, if that value is not nil, using an if let statement or a guard let:

// Swift

var x: String?

... // Assign some values or nil to x.

// print(x.count) // This does not compile.

guard let nonOptionalX = x else {
    return
}

// nonOptionalX is of type String, not String?.
print(nonOptionalX.count) // This will compile.

In Dart, the same behavior can be obtained by simply checking if the variable is null:

// Dart

String? x;

... // Assign some values or null to x.

// print(x.length); // This would NOT compile.

if (x == null) {
    // x is `null`
    return;
}
    
// x is non-null, and the compiler knows that.
print(x.length); // This will compile.

Optional chaining

Optional chaining is used to safely access properties or methods of an optional, and it is the same in both languages. It was already available in Dart before the introduction of sound null safety.

// Swift

var x: String?

... // Assign some values or `nil` to x.

// let length = x.count // This does not compile.

let length = x?.count 
// length is an Int? and it contains either nil, or the length of the string in x
// Dart

String? x;

... // Assign some values or null to x.

// var length = x.length; // This would NOT compile.

var length = x?.length; 
// length is an Int? and it contains either null, or the length of the string in x

Nil-coalescing operator

As per the optional chaining, the nil-coalescing operator is available in both languages, and it was also available before the introduction of sound null safety.

// Swift

var x: Bool?

... // Assign some values or `nil` to x.

print(x ?? false) // prints either the value in x, or 'false' if x is nil.
// Dart

bool? x;

... // Assign some values or null to x.

print(x ?? false); // prints either the value in x, or 'false' if x is nil.

late keyword

In Dart, you can use the late keyword in front of a non-nullable property, to inform the compiler that that property is not initialized immediately, but you will assigne a non-null value to it before accessing it.

From the official article by the Dart team:

// Dart

class Goo {
    late Viscosity v;
    
    Goo(Material m) {
        v = m.computeViscosity();
    }
}

In my opinion, you should avoid this use of late as much as possible. Use an Optional instead. The code might be clear to you while you write it, but it lacks in maintainability.

You can also use late on a property that has an initializer:

// Dart

class Goo {
    late Viscosity v = _m.computeViscosity();
}

In this case, the property v becomes lazy and it is initialized only when it is accessed for the first time. This is the same as using lazy in Swift:

// Swift

class Goo {
    lazy var v = m.computeViscosity()
}

Extending the optional type

In both languages, we can extend the functionalities of the optional type:

// Swift

extension Optional where Wrapped == Bool {
    /// Returns the value, or `false` if the value is `nil`
    func orFalse() -> Bool {
        return self ?? false
    }
}

let x: Bool? = nil
print(x.orFalse()) // prints `false`.
// Dart

extension UnwrapOrFalse on bool? {
    /// Returns the value, or `false` if the value is `null`
    bool orFalse() {
        return this ?? false;
    }
}

bool? x;
print(x.orFalse()); // prints `false`.

How programming in Dart will change

I am a big fan of Swift Optional. I like that the developer is forced to specified when a certain variable or parameter could be nil. Having a very similar feature in Dart will be welcomed by the entire community.

Say goodbye to all those, now useless, asserts on initializers and method definitions.

// Dart

Article({this.id, this.description}):
    assert(id != null),
    assert(description != null);

Get a weekly email about Flutter

Subscribe to get a weekly curated list of articles and videos about Flutter and Dart.

    We respect your privacy. Unsubscribe at any time.

    Leave a comment