Search code examples
swiftoption-typeoptional-binding

Is there a technical downside to using implicitly-unwrapped optionals but testing for nil vs optional binding?


Okay, so I know the normal way to use optionals in Swift is via optional bindings to unwrap them, like so...

let stringA:String? = nil // (or "ABC")

if let unwrappedStringA = stringA
{
    let x:String = unwrappedStringA
}

But I've also seen the following way using implicitly-unwrapped optionals, which I personally think looks cleaner as it not only doesn't require an extra variable, but it also 'reads' better and more like English (i.e. 'If this is not nil, then...') and readability is one of the core tenets of Swift (especially in the upcoming Swift 3.0).

let stringA:String! = nil // (or "ABC")

if stringA != nil
{
    let x:String = stringA
}

However, in regards to the latter, Swift 'purists' refer to this as 'code smell' and insist it's 'Bad, bad, bad!!'... but they never explain why! So... why is it so bad?

Note: Yes, I know about Optional chaining and other such features which you can't use with implicitly-unwrapped optionals, and they are all really cool features, but I am specifically asking about the technical downside to testing implicitly-unwrapped optionals against nil vs optional binding.

What I'm hoping for are quantifiable, technical reasons why one is better than the other (i.e. compiler optimizations, better safety checking, performance, etc.) In other words, not just a 'Hey, because that's not the 'Swifty' way to do it!' Hope that makes sense.

Update

I actually just found a way to address one of my concerns (at least in a superficial way) which is having to create a new variable to hold the unwrapped one. The trick here is since the unwrapped variable is actually in a different scope than the optional itself, you can use the exact same name.

Further still, inside the brackets themselves is yet another scope which can also have a variable named stringA. This means you can now have three 'stringA' variables (but doesn't mean you should!)...

  1. The optional
  2. The unwrapped optional
  3. A new, local variable inside the brackets

Here's this crazy code showing this...

let stringA:String? = nil // (or "ABC") // First stringA variable

if let stringA = stringA
{
    let x:String = stringA // <- This stringA is the unwrapped optional (the second stringA)

    // Now we're just getting crazy (and smelly!)
    let stringA = stringA + stringA // <- This is yet another stringA (third one!)
    let y:String = stringA 
}

Again, I am not condoning this!! I'm just showing some code that others may find interesting from an instructive perspective. I sure did!


Solution

  • There is indeed a simple reason, and it's one of the ones you listed: "better safety checking". Optional bindings keep the scope of the unwrap known to the compiler, whereas it's the programmer's responsibility to keep track of when you have or have not checked an implicitly unwrapped optional for nil.

    With optional bindings:

    let stringA:String? = nil // (or "ABC")
    
    if let unwrappedStringA = stringA
    {
        let x:String = unwrappedStringA
    }
    
    accidentallyAttemptingToUse(stringA) // Compiler error! I see my mistake.
    

    With implicit unwrapping:

    let stringA:String! = nil // (or "ABC")
    
    if(stringA != nil)
    {
        let x:String = stringA
    }
    
    accidentallyAttemptingToUse(stringA) // Runtime crash I might find in testing. Yuck.
    

    What are IUOs good for?

    Why have implicitly unwrapped optionals at all, then? They exist for basically one purpose: properties that will definitely have a value, but not until slightly after init, so they have to be optional. Often @IBOutlets fall into this category: you can trust them to have a value, and you don't want to be unwrapping them all the time, but they are not assigned at init, so you must make them optional. That is what implicitly unwrapped optionals are for.

    Unwrapping to a variable of the same name

    Your update, which tentatively suggests unwrapping optionals into variables with the same name, is actually excellent Swift style, and has been used by Apple early and often. Code is easier to read when you unwrap to variables of the same name, because you can track fewer (and better) names. Definitely embrace this!

    let stringA:String? = nil // (or "ABC")
    
    if let stringA = stringA
    {
        let x:String = stringA // Of *course* this is still named `stringA`;
        // it's representing the same concept, after all.
    }