UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Question about implicitly unwrapped optionals (not) behaving "as if they weren't optional at all"

Forums > Swift

Can somebody clear something up about implicitly unwrapped optionals for me? Running the code

var number: Int! = 5
print("My age is \(number).")

let withoutTypeAnnotation = number
print("My age is \(withoutTypeAnnotation).")

let withTypeAnnotation: Int = number
print("My age is \(withTypeAnnotation).")

prints:

My age is Optional(5).
My age is Optional(5).
My age is 5.

In the description of implicitly unwrapped optionals it says that:

However, unlike regular optionals you don’t need to unwrap them in order to use them: you can use them as if they weren’t optional at all.

In the documentation it reads:

Sometimes it’s clear from a program’s structure that an optional will always have a value, after that value is first set. In these cases, it’s useful to remove the need to check and unwrap the optional’s value every time it’s accessed, because it can be safely assumed to have a value all of the time.

Why do the first two print() statements print “Optional(5)“? Here it doesn’t seem to behave “as if it weren’t optional at all”, and it doesn't seem to get unwrapped automatically when my print() call accesses it. 🤔

4      

@twostraws  Site AdminHWS+

Swift's behavior here is surprising, but intentional: implicit unwrapped optionals are just regular optionals with a special flag set, but when you copy the optional that flag isn't copied. In your code, copying the IUO makes the copy into a regular optional, which is what you're seeing.

6      

I see, thank you for that explanation! 🙂 Then why does the very first print() statement also print "Optional(5)"? Is it because it's passed to print() by value, and therefore copied and turned into a regular optional as well?

3      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

@twostraws  Site AdminHWS+

Implicitly unwrapped optionals are still optionals, they just get to skip the compiler checks.

3      

I feel like this would make a nice Swift interview question.

3      

I'm afraid I then don't understand what

However, unlike regular optionals you don’t need to unwrap them in order to use them: you can use them as if they weren’t optional at all.

really means then. I promise I'm not trying to be obtuse here.

var num: Int! = 5
print("My number is \(num).")
// prints out "My number is Optional(5)."

var num2: Int! = 5
print("My number is \(num2!).")
// prints out "My number is 5."

The second example here looks to me like I do have to unwrap num (in this case via!) in order to use it.

Maybe I don't quite understand what "using" a property actually refers to? 🤔 In my mind it reads as "get at the value held by the optional if there is one" but maybe I'm mistaken in that intuition?

3      

That's because it is an optional at runtime. Is is treated as a regular value at compile time.

So if you do this:

    let a: Int? = nil
    let b: Int = a

The compile will complain on the second line. But if you do this:

    let a: Int! = nil
    let b: Int = a

The code will be compiled without errors, and you will see the error at run time.

So if you use !, it's your responsibility to make sure a isn't nil, because the compiler won't warn you.

4      

I know this can sound a little goofy.

An Optional is a type, same as an Int or String. While we might casually say "optional string" or "optional int," that is shorthand for "an "Optional that may contain an Int" or an "Optional that may contain a String." Just as an Int or a String has limits on what they can contain, and rules for using them, so does an Optional. Remember, an Optional is its own unique type.

The key rule with an Optional is that in order to use its contents, it has to be unwrapped. There are two main ways to do that: by copying its value into another non-optional variable with if-let or similar, or marking it as implicitly unwrapped (!), which means that its value will be directly accessed at runtime. Should that value be nil, the program will crash immediately. That is by design. It is the way an Optional type is intended to work.

Remember that Swift is strict about types, when a variable is created, its type is fixed forever. So an Int is always an Int, and an Optional is always an Optional.

So what use is an Implicitly Unwrapped Optional? Well they are useful when mixing Swift and Objective-C. Even if you are writing pure Swift, a lot of Objective-C code is called on your behalf. Your variables will often be passed to an Objective-C function. They are also useful for communicating your intent to someone reading the code later. If you mark an Optional with !, you're comminuicating that you planned that this variable will never be nil when its needed.

3      

Thank you for the responses! 🙂 I still feel like I'm getting mixed messages here, or I'm merely misunderstanding. @guseulalio is telling me:

That's because it is an optional at runtime.

@bobdel is telling me:

... marking it as implicitly unwrapped (!), which means that its value will be directly accessed at runtime.

One states that an implicitly unwrapped optional behaves as a normal optional at runtime, the other says that an IUO's value will be always directly accessed at runtime (without needing explicit unwrapping), which then should also hold when using print(). 🤔

I do understand that optionals are a distinct data type, that part isn't where I'm struggling. What I'm wondering is how exactly implicitly unwrapped optionals are implemented, and how their behaviour differs exactly from that of common optionals.

Maybe let me try a more concrete yes-or-no question:

Is it true that the only case in which the behaviour of an implicitly unwrapped optional differs from the behaviour of a common optional is if I try to assign the implicitly unwrapped optional to a variable or constant¹ that's been explicitly declared with a non-optional type?

¹) By the way, is there a single general term in Swift that subsumes both 'variable' and 'constant'? I know 'property' is used sometimes to that effect, but it seems to only refer to a constant or variable that exists as part of a class or structure.

3      

I think part of the confusion here is not really over optionals, it’s over what print() is doing.

When you call print(object), it doesn’t immediately know what to do with your object — since print can accept anything (with varying results), the type of its variadic items parameter is Any.... If you’re printing, it knows it ultimately needs a string to display in the console, so it delegates to String(describing: instance). Let’s take a look at the docstring to see what that method does:

/// - If `instance` conforms to the `TextOutputStreamable` protocol, the
///   result is obtained by calling `instance.write(to: s)` on an empty
///   string `s`.
/// - If `instance` conforms to the `CustomStringConvertible` protocol, the
///   result is `instance.description`.
/// - If `instance` conforms to the `CustomDebugStringConvertible` protocol,
///   the result is `instance.debugDescription`.
/// - An unspecified result is supplied automatically by the Swift standard
///   library.

Let’s go down that list, remembering that print has coerced your object (which is an Optional<Int> with the implicit-unwrap flag) to Any:

var number: Int! = 5
number as Any is TextOutputStreamable // false 
number as Any is CustomStringConvertible // false
number as Any is CustomDebugStringConvertible // true
number.debugDescription // "Optional(5)"

So, the string "Optional(5)" is what gets returned from String(describing:) and what print prints. You can, of course, unwrap number before it becomes coerced to Any:

type(of: number!) // Int.Type
number! as Any is CustomStringConvertible // true
number!.description // "5"
print(number!) // "5"

By the way, here’s the documentation of the Swift Evolution implementation when implicitly-unwrapped optionals changed to their current behavior, which is quite helpful.

5      

One states that an implicitly unwrapped optional behaves as a normal optional at runtime, the other says that an IUO's value will be always directly accessed at runtime (without needing explicit unwrapping), which then should also hold when using print(). 🤔

I would restate the quote above this way:

One states that an implicitly unwrapped optional is an optional at runtime (True), the other says that an IUO's value will be always directly accessed at runtime (without needing explicit unwrapping) (True)

What I'm wondering is how exactly implicitly unwrapped optionals are implemented, and how their behaviour differs exactly from that of common optionals.

You can look directly at the Optional implementation in the Standard Library. Note the extension on line 267. When printed to the console, a type can have customized output. When you print an optional using Print(), it appears the same regardless if it is implicitly unwrapped or not.

I hope this is clear, but if not ask.

3      

Thank you so much @bbatsell for that elaborate explanation, that did teach me a lot! 🙂 It seems that indeed my confusion stemmed primarily from the workings of print(). Thanks also to @bobdel and everyone else who contributed. Very helpful indeed! :)

3      

This has been a very interesting thread. Coming from a core Java background (very C-like), I have been very light on my understanding of Optionals. This discussion helped solidify a good bit of that. Thanks all!

4      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.