Nil is a lie. In Swift. Well kind of… let me explain.

  • by
stage door band and crew ONLY

I do hope you have big patience because I will start slow. It will like a tutorial on how to draw an owl. But I’m sure you will connect the dots.

What’s different in those functions:

func i1() -> Int? { 42        }
func i2() -> Int? { .some(42) }
func i3() -> Int? { nil       }
func i4() -> Int? { .none     }

On the outside nothing but on the inside everything is different or so it seems. Once I return plane old value 42, at some other time its .some(42). Then nil and finally .none. I think you already know where I’m going with this but let’s continue. For me, the most interesting part is that I can return nil. If you know only Swift that may have no meaning to you. But if you know Objective-C then it’s like the center of the universe!

The Big Idea of Nothing

It turns out that nothing is really something in IT. And there are a lot of ways to express this idea of nothing. Back in a day, someone decided that you can use a special area in the computer memory to say this is nothing. What’s funny the address of this memory is 0x0. What that meant was that every time you had a reference to the instance that pointed to this address you had nothing. Confused? Well, a lot of programmers were and are also to this day.

Problem with this nil is that if you actually try to see what is nothing (dereference a null pointer) your computer will explode. Well kind of. Your application will crash with a dreaded null pointer exception. In a similar way to this day in one form or another this haunts developers across the world.

But Objective-C is different in how it treats nil. You can safely send a message to nil and the runtime will check if it’s nil or a real object waiting at the other end. If it was nil then this message call would return you nil. So you could send a message to nil and your app would still work just fine. Even more you would write code based on this property of Objective-C. Every system framework followed this pattern. That’s what I mean that nil is in the center of the universe.

Here comes Swift

Swift has a very sweet relationship with missing values and nil. It uses a special sum type or more commonly named enumeration called Optional. Implementation of this enum could be boiled down to this:

enum Optional<Wrapped> {
    case none
    case some(Wrapped)
}

This is something new for object-oriented Objective-C developers that are used to working with pointers. Not so new for functional developers thou. So I’m guessing that designers of a new language wanted to give something that ObjC developers could hold on to. And like I said a ton of system frameworks already used nil and returned nil.

Getting back to the “owl”

It’s time to remove some sugar from this Optional:

func i5() -> Optional<Int> { 42        }
func i6() -> Optional<Int> { .some(42) }
func i7() -> Optional<Int> { nil       }
func i8() -> Optional<Int> { .none     }

Ok but I can clearly see nil right there! And what’s more surprising I can check in code if some thing is nil or not like this:

i3() == .none // true
i4() == nil   // true
 
nil == ()     // nil is not an empty tuple

Our very own nil

If you will check the documentation you can find something like this:

Protocol
ExpressibleByNilLiteral
A type that can be initialized using the nil literal, nil.

Overview
nil has a specific meaning in Swift—the absence of a value. Only the Optional type conforms to ExpressibleByNilLiteral. ExpressibleByNilLiteral conformance for types that use nil for other purposes is discouraged.

Finally we are getting somewhere. At least there are some hints about something called nil literal. And that Only the Optional type conforms to ExpressibleByNilLiteral. This as it will turn out is not exactly true. But it seems that this whole thing is there so that literal characters nil can be used to insatiate an Optional. If you want you can check the literals section of Swift lexical structure to find out more.

Let’s do this thing and implement our very own Optional

enum MyOptional<Wrapped> {
    case some(Wrapped)
    case none
}

I won’t be original and copy how original Optional conformance is implemented. We are learning stuff so I guess we can do this.

extension MyOptional: ExpressibleByNilLiteral {
    init(nilLiteral: ()) {
        self = .none
    }
}

// Now I can return nil and get instance of my type! Sweet!
func mi2() -> MyOptional<Int> { nil }

I hope there’s is no more magic with that. When the required init is called it just passes in an empty tuple. Which is also known as Void… so we get an instance of a void there and do only one reasonable thing to do. Later I return nil but the compiler knows it’s my type so I’m getting an instance of my type.

There was one more thing that Optional is able that MyOptional cannot do. That is to check if mi2() == nil. There is one more hoop that is needed for that. And it might be a bit surprising:

struct _MyOptionalNilComparisonType: ExpressibleByNilLiteral {
    init(nilLiteral: ()) {}
}

extension MyOptional {
    static func ~=(lhs: _MyOptionalNilComparisonType, rhs: MyOptional<Wrapped>) -> Bool {
      switch rhs {
      case .some: return false
      case .none: return true
      }
    }

    static func ==(lhs: MyOptional<Wrapped>, rhs: _MyOptionalNilComparisonType) -> Bool {
      switch lhs {
      case .some: return false
      case .none: return true
      }
    }

    static func ==(lhs: _MyOptionalNilComparisonType, rhs: MyOptional<Wrapped>) -> Bool {
      switch rhs {
      case .some: return false
      case .none: return true
      }
    }

    static func !=(lhs: MyOptional<Wrapped>, rhs: _MyOptionalNilComparisonType) -> Bool {
      switch lhs {
      case .some: return true
      case .none: return false
      }
    }

    static func !=(lhs: _MyOptionalNilComparisonType, rhs: MyOptional<Wrapped>) -> Bool {
      switch rhs {
      case .some: return true
      case .none: return false
      }
    }
}

Wow that’s a lot of code and it’s not all of it! I know you won’t gonna read it. I will break it down to more manageable chunks. I have introduced a new type_MyOptionalNilComparisonType that conforms to ExpressibleByNilLiteral. Later I define a bunch of static functions for compensation. The only thing that’s different is the order of arguments. Sometimes my type is the first one sometimes it’s not.

Why I do it this way? Well Swift has this property that it will infer types based on this if the code compiles. Meaning Swift knows that mi2() == nil can compile only if there is a == that expects my type as the first argument and something that can be created from nil literal. Other overloads are for different combinations.

Did I say that I have introduced this extra type and wrote those functions? What I have meant wast that it’s implemented like this in Swift. You can check it here where it says that enables pattern matching against nil literal. And if you wondered how it’s done that in the debugger you see printed out nil not “.none”. Well you guessed it, it returns string “nil”. So it goes full circle.

More proof

What more proof? No problem! I was hoping you will ask for more. What if I tell you that we can break to atoms what compiler compiled and decompile that! Or stop just before and check the next greatest thing than assembly code which is Swift Intermediate Language or SIL for friends.

Before any code from text form gets turned into to very long series of 1 and 0 some steps need to happen. I wont go to detail how and what exactly but you can imagine that it’s a more elaborate process divided into different stages. We will be using a tool called SILInspector that will allow us to write code and check different stages of this compilation process.

We need to have something to decompose:

func mystery() -> Int? {
    42
}

SILInspector gives this output:

SIL output for mystery function

Scary stuff. But we can manage it. First thing is to locate mystery function.

// mystery()
sil hidden @main.mystery() -> Swift.Int? : $@convention(thin) () -> Optional<Int> {
bb0:
  %0 = integer_literal $Builtin.Int64, 42         // user: %1
  %1 = struct $Int (%0 : $Builtin.Int64)          // user: %2
  %2 = enum $Optional<Int>, #Optional.some!enumelt.1, %1 : $Int // user: %3
  return %2 : $Optional<Int>                      // id: %3
} // end sil function 'main.mystery() -> Swift.Int?'

Pice by pice:

  • Line 7 returns something that is held by %2 registry.
  • Line 6 clearly states Optional.some!enumelt.1 so there you have it. Some case from the Optional enum.
  • Lines 4-5 are this magic that turns integer literals to numbers. You can ignore this.

This is how return 42 gets boxed inside an Optional. What happens when nil is returned? Let’s see:

func mystery() -> Int? {
    nil
}

And the generated SIL:

// mystery()
sil hidden @main.mystery() -> Swift.Int? : $@convention(thin) () -> Optional<Int> {
bb0:
  %0 = enum $Optional<Int>, #Optional.none!enumelt // user: %1
  return %0 : $Optional<Int>                      // id: %1
} // end sil function 'main.mystery() -> Swift.Int?'

I think by now you can see whats going on by yourself. Hint: line 4 is the meaty part.

Summary

Nil does not exist… well sort of. It’s just a syntactic sugar for handling missing values that is more familiar for ObjC developers. In my opinion, returning .none is more clear than returning nil. Especially when nil is so overloaded (check out this article about nil on NSHipster).

Another thing is that there are more useful Mondas (what I did not mention that Optional is a Monad?) that have the same shape but no special treatment from Swift language. Maybe seeing how those other objects are similar to Optional would help get more intuition for things that will come.

Thank you for reading this. Take care 🙂

Only if you know polish

You can read the same article in polish nil czy .none? and/or watch it on Lekko Technologiczny YT Channel. And there’s even an Xcode Playground to check it all out.

Leave a Reply

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