Keiwan Keiwan - 1 year ago 70
Swift Question

Swift 3 incorrect string interpolation with implicitly unwrapped Optionals

Why are implicitly unwrapped optionals not unwrapped when using string interpolation in Swift 3?

Running the following code in the playground

var str: String!
str = "Hello"

print("The following should not be printed as an optional: \(str)")

produces this output:

The following should not be printed as an optional: Optional("Hello")

Of course I can concatenate strings with the
operator but I'm using string interpolation pretty much everywhere in my app which now doesn't work anymore due to this (bug?).

Is this even a bug or did they intentionally change this for Swift 3?

Answer Source

As per SE-0054, ImplicitlyUnwrappedOptional is no longer a distinct type. Instead, it's now the same type as a regular Optional – it just has an attribute that allows the compiler to force unwrap it in situations where it cannot be type checked as one.

As the proposal says (emphasis mine):

If the expression can be explicitly type checked with a strong optional type, it will be. However, the type checker will fall back to forcing the optional if necessary. The effect of this behavior is that the result of any expression that refers to a value declared as T! will either have type T or type T?.

What this means is that when it comes to type inference, the compiler will always favour typing an implicitly unwrapped optional as an Optional, rather than force unwrapping it. However, this behaviour will be overridden when an explicit type annotation is supplied.

When it comes to string interpolation, under the hood the compiler uses this initialiser from the _ExpressibleByStringInterpolation protocol in order to evaluate a string interpolation segment:

/// Creates an instance containing the appropriate representation for the
/// given value.
/// Do not call this initializer directly. It is used by the compiler for
/// each string interpolation segment when you use string interpolation. For
/// example:
///     let s = "\(5) x \(2) = \(5 * 2)"
///     print(s)
///     // Prints "5 x 2 = 10"
/// This initializer is called five times when processing the string literal
/// in the example above; once each for the following: the integer `5`, the
/// string `" x "`, the integer `2`, the string `" = "`, and the result of
/// the expression `5 * 2`.
/// - Parameter expr: The expression to represent.
init<T>(stringInterpolationSegment expr: T)

As this initialiser uses a generic parameter (no explicit type annotation), the compiler is able to infer the parameter T to be of type Optional for an implicitly unwrapped optional input, therefore meaning that it won't be force unwrapped.

Conversely, if you take for example print() which takes an Any parameter (an explicit, albeit abstract type annotation), the compiler won’t be able to infer an IUO input to be of type Optional, and therefore it will be implicitly force unwrapped.

If you wish for an IUO to be force unwrapped when used in string interpolation, you can simply use the force unwrap operator !:

var str: String!
str = "Hello"

print("The following should not be printed as an optional: \(str!)")

or you can cast to its non-optional type (in this case String) in order to force the compiler to implicitly force unwrap it for you:

print("The following should not be printed as an optional: \(str as String)")

both of which, of course, will crash if str is nil.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download