Things I don’t like in Swift – nil and Any

Introduction

We all know what Swift is and what improvements it brings into the iOS development world. Besides its powerful protocol oriented design and memory safety, it has some kind of flows which are ugly, in my honest opinion. Today’s talk is about “nil”.

Nil

First, what is nil? It’s just null as in other programming languages. But most other languages treat nulls as special values rather than an absence of the them. There’s a great talk about this, check it out here: NULL: The worst mistake of computer science? (2015).

But Swift isn’t that most other languages. It has special kind of optional values that can tell if there is a value.

import Foundation

let optionalValue: NSValue? = nil
let explicitOptionalValue: Optional<NSValue> = nil
let explicitOptionalValueCase: NSValue? = .none

optionalValue == explicitOptionalValue // true
explicitOptionalValueCase == nil // true

As you can see here, there’s a special generic enum type called “Optional”. Every time when you write a question mark “?”, the swift compiler automatically treats it as this type. The source code defines two possible cases: some and none. As you guess none stands for absence of the value. In other words, there is no special null value like in, let’s say, Java.

If you are familiar with Swift or even you use it in your daily job, you barely never assign .none, do you? Instead, you always use nil. Open the source again, look at strange protocol called “ExpressibleByNilLiteral”. Yes, you understood it correctly – you can initialize objects from nil literal.

extension String: ExpressibleByNilLiteral {
    public init(nilLiteral: ()) {
        self.init("<nil>")
    }
}

let nonOptionalString: String = nil // "<nil>"
let optionalString: String? = nil // .none
let optionalNonOptionalString: String? = Optional.some(nil) // Optional.some("<nil>")

nonOptionalString == optionalString // false, as "<nil>" != Optional.none
optionalNonOptionalString! == nil // true
optionalNonOptionalString! == optionalString // false

if let thereIsValue = optionalNonOptionalString {
    thereIsValue == nil // true
    thereIsValue == "<nil>" // true
}

if let thereIsValue = nonOptionalString {
    // error: initializer for conditional binding must have Optional type, not 'String'
    thereIsValue == nil
}

However, conforming String to ExpressibleByNilLiteral and it mixing Optional is not a good idea. But it is just possible, we need to keep it in mind.

Any

And here comes the special type – “Any”. It’s just an empty protocol like in Go. And this “Any” type can hold Optional types too. But swift compiler won’t allow downcasting from Any to Optional.

let someAny: Any = nil
// error: nil cannot initialize specified type 'Any'
// error occures just because Any cannot hold nil literal

let someAnyOptional: Any = Optional<NSValue>.none

if let fromAnyToOptional = someAnyOptional as? NSValue? {
    // error: cannot downcast from 'Any' to a more optional type 'NSValue?'
    print(fromAnyToOptional)
}

But, at the same time, it allows downcasting to our String type. And even some generic type.

extension Array: ExpressibleByNilLiteral {
    public init(nilLiteral: ()) {
        self.init([])
    }
}

let nilStringAny: Any = (nil as String)
let nilArrayAny: Any = (nil as Array<NSValue>)

if let fromNilStringAny = nilStringAny as? String {
    fromNilStringAny == nil // true
}

if let fromNilArrayAny = nilArrayAny as? Array<NSValue> {
    fromNilArrayAny == nil // true
}

if let typeMismatchAny = nilStringAny as? Array<NSValue> {
    // will never reach, as String != Array<NSValue>
}

Conclusion

The biggest problem is here that while removing runtime nulls, Swift introduced a new way of a headache by treating Optional type especially. You may ask, why one needs to convert from Any to nil? Well, there’s a popular swift project “Wrap” which encodes objects to JSON using Swift.Mirror (analog of reflection in Java). And I needed explicit nulls in JSON while default implementation just omitted them. While preparing my pull request I faced a problem, the main verify function accepts “Any”s and I needed to somehow check if it was nil. Thankfully it was just verification of the library and I could shamelessly use that ugly workaround.

TL;DR; nils are converted to Optional.none and treated especially. Downcasting from Any produces error for that reason

Leave a Reply

Your email address will not be published. Required fields are marked *