Swift 3 Incorrect String Interpolation With Implicitly Unwrapped Optionals

Swift 3 incorrect string interpolation with implicitly unwrapped Optionals

As per SE-0054, ImplicitlyUnwrappedOptional<T> is no longer a distinct type; there is only Optional<T> now.

Declarations are still allowed to be annotated as implicitly unwrapped optionals T!, but doing so just adds a hidden attribute to inform the compiler that their value may be force unwrapped in contexts that demand their unwrapped type T; their actual type is now T?.

So you can think of this declaration:

var str: String!

as actually looking like this:

@_implicitlyUnwrapped // this attribute name is fictitious 
var str: String?

Only the compiler sees this @_implicitlyUnwrapped attribute, but what it allows for is the implicit unwrapping of str's value in contexts that demand a String (its unwrapped type):

// `str` cannot be type-checked as a strong optional, so the compiler will
// implicitly force unwrap it (causing a crash in this case)
let x: String = str

// We're accessing a member on the unwrapped type of `str`, so it'll also be
// implicitly force unwrapped here
print(str.count)

But in all other cases where str can be type-checked as a strong optional, it will be:

// `x` is inferred to be a `String?` (because we really are assigning a `String?`)
let x = str

let y: Any = str // `str` is implicitly coerced from `String?` to `Any`

print(str) // Same as the previous example, as `print` takes an `Any` parameter.

And the compiler will always prefer treating it as such over force unwrapping.

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?.

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)

Therefore when implicitly called by your code:

var str: String!
str = "Hello"

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

As str's actual type is String?, by default that's what the compiler will infer the generic placeholder T to be. Therefore the value of str won't be force unwrapped, and you'll end up seeing the description for an optional.

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 coerce 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.

Swift 3: Cannot automatically unwrap optional before setting it to Label

In Swift 3, an implicitly-unwrapped optional is the same as an optional, except that it will implicitly unwrap in contexts that require it. For instance, if you had a func foo(i: Int), you could write foo(i: leftNumber) and the compiler would perform the unwrap operation for you.

String interpolation is not a context where unwrapping is required. As you can use an optional there, Swift prefers the optional case and does not unwrap for you.

Tutorials that you find online may either not have been updated for Swift 3, or there may have been a misunderstanding.

Why should I unwrap implicitly unwrapped optional?

print, as part of its own internal workings, unwraps Optionals (the regular, unwrap-yourself kind). String interpolation doesn't do this for you, it just converts whatever you give it.

Here's an explanation of the last example:

  • print("y: \(y!.description)") // y: 2

    y has a type Int!, which is explicitly unwrapped, to give its Int content. description is called on it. description returns a String. If y was nil, this would crash.

  • print("y: \(y?.description)") // y: Optional("2")

    Optional chaining is used to call description on y, only if it's non-nil. If it's nil, then description isn't called in the first place, and the nil is propagated. The result of this expression is a String?.

  • print("y: \(y.description)") // y: 2

    Like case 1, y starts as an Int!, but is this time implicitly unwrapped, to give its Int content. description is called on it. description returns a String. If y was nil, this would crash.

Why does a `nil` implicitly unwrapped optional print `nil` and not crash?

That does not crash because print accepts Any as the first parameter. Are implicitly unwrapped optionals a kind of Any? Yes they are! Anything is Any. There is no need to unwrap the optional. An implicitly unwrapped optional can be used in a place where Any is expected without unwrapping the optional.

That could potentially be confusing because now you have something with type Any, which doesn't look like it's optional, but it is an optional under the hood. To avoid this, Swift will output a warning telling you that you are implicitly coercing whatever optional type to Any.

You need to use ! to force unwrap it here:

print(x!)

Why are implicitly unwrapped variables now printing out as some(...) in Swift 4.1?

String! is an implicitly unwrapped optional but it's still an optional.

The value will get unwrapped to a non-optional only in situations when it has to be unwrapped, e.g. when being passed to a function that cannot take an optional. However, print can accept an optional and String! will be treated just as String?.

This change actually happened in Swift 3 already as part of SE-0054.

In your example:

var aString: Int!
let aBool = true
if aBool {
aString = 2
}

print(aString)

You should not be using an implicitly unwrapped optional because since it's a var, it get initialized to nil. You should either handle the unassigned case explicitly by using Int?, or, give it a default value:

let aString: Int
let aBool = true
if aBool {
aString = 2
} else {
aString = 0
}

print(aString)

Why does implicitly unwrapped optional not unwrap in dictionary of type [String : Any]

Under the rules set out by SE-0054, IUOs are only force unwrapped in contexts that demand their unwrapped type. In your case, the IUO doesn't need to be force unwrapped in order to be coerced to Any (as Any can represent any value), so it isn't.

This behaviour is discussed in more detail in these Q&As:

  • Swift 3 incorrect string interpolation with implicitly unwrapped Optionals
  • Implicitly unwrapped optional assign in Xcode 8

The fact that you end up with an ImplicitlyUnwrappedOptional value in your dictionary is legacy behaviour that has been removed in the latest Swift snapshots, in the future you will end up with an Optional value instead (as IUO is no longer a type).

One important thing to note here however (that I'm sure will trip up people) is that the printing of IUOs got changed in 4.1.

In Swift 4.0.3, your example prints like this:

var aString: String! = "hello"
var params : [String : Any] = [
"myString" : aString
]
print(params)
// This prints ["myString": hello]

giving you the illusion that the IUO was force unwrapped when coerced to Any. This however is just how IUOs were printed in Swift 4.0.3 – if they had a value, then they would print as that value, otherwise they would print as nil:

var aString: String! = nil
var params : [String : Any] = [
"myString" : aString
]
print(params)
// This prints ["myString": nil]

The reason why this changed in Swift 4.1 is that ImplicitlyUnwrappedOptional's conformance to Custom(Debug)StringConvertible was removed in this commit in order to make progress towards removing the type itself. So now ImplicitlyUnwrappedOptional values get printed using Swift's default printing mechanism (using reflection).

So, in a dictionary, you get the IUO's default debugDescription, which looks like this:

let aString: String! = "hello"
let params : [String : Any] = [
"myString" : aString
]
print(params)
// This prints ["myString": Swift.ImplicitlyUnwrappedOptional<Swift.String>.some("hello")]

If you had printed it on its own, you would get its default description, which looks like this:

let aString: String! = "hello"
print(aString) // some("hello")

This is because in Swift 4.1, the ImplicitlyUnwrappedOptional type is implemented in the same way as Optional, an enumeration with two cases:

public enum ImplicitlyUnwrappedOptional<Wrapped> : ExpressibleByNilLiteral {
// The compiler has special knowledge of the existence of
// `ImplicitlyUnwrappedOptional<Wrapped>`, but always interacts with it using
// the library intrinsics below.

/// The absence of a value. Typically written using the nil literal, `nil`.
case none

/// The presence of a value, stored as `Wrapped`.
case some(Wrapped)

// ...
}

For an IUO with a payload value, Swift's default reflection will therefore print it as the case some containing the wrapped value.

But this is only temporary; the IUO type is currently (in Swift 4.1) deprecated, however it will be removed in Swift 4.2. The compiler was internally using the IUO type in quite a few places, which took quite a bit of work to remove. Therefore in 4.2 you'll have actual Optional values in your dictionary, which will print like Optional("hello").

Why should I unwrap implicitly unwrapped optional?

print, as part of its own internal workings, unwraps Optionals (the regular, unwrap-yourself kind). String interpolation doesn't do this for you, it just converts whatever you give it.

Here's an explanation of the last example:

  • print("y: \(y!.description)") // y: 2

    y has a type Int!, which is explicitly unwrapped, to give its Int content. description is called on it. description returns a String. If y was nil, this would crash.

  • print("y: \(y?.description)") // y: Optional("2")

    Optional chaining is used to call description on y, only if it's non-nil. If it's nil, then description isn't called in the first place, and the nil is propagated. The result of this expression is a String?.

  • print("y: \(y.description)") // y: 2

    Like case 1, y starts as an Int!, but is this time implicitly unwrapped, to give its Int content. description is called on it. description returns a String. If y was nil, this would crash.

why optional appears when forming a string from an Int

You have declared n1 as an implicitly unwrapped optional:

var n1: Int!

This means n1 is still an optional, but you are promising the code that it will be non-nil. In order to avoid the optional state you would need to declare at as:

var n1: Int

In that case you either need to make sure to initialize it in your init() function or provide a default init like this:

var n1: Int = 0

Swift 3 Getting Optionals After Initializing Class

In Swift 2, the type Int! was shorthand for ImplicitlyUnwrappedOptional<Int>. The compiler treated ImplicitlyUnwrappedOptional specially: it could be treated like Optional in contexts that allowed an Optional, and automatically (implicitly) unwrapped in contexts that didn't allow an Optional.

The problem was that it was hard to understand what the compiler would do with an Int! in various circumstances. Here's one of many examples:

func f() -> Int! { return 3 }
let x1 = f()

What is the type of x1 in that code, in Swift 2? Is it Int, ImplicitlyUnwrappedOptional<Int> (aka Int!), or Optional<Int> (aka Int?)? It turns out that in Swift 2, x1 is type Int?. You'd have to be a language guru to know that.

You can find more examples in this swift-evolution thread discussing the abolition of the ImplicitlyUnwrappedOptional type.

For Swift 3, the Swift team adopted SE-0054: Abolish ImplicitlyUnwrappedOptional type. SE-0054 removed the ImplicitlyUnwrappedOptional type entirely and changed the meaning of var id: Int!. Now it declares that id has type Optional<Int>, but if you use id where only a plain Int is accepted, then the compiler implicitly unwraps it for you. In all other contexts, id is treated as a Int? (aka Optional<Int>). Whenever the compiler can treat id as Int?, it will do so.

This is why your results are different between Swift 2 and Swift 3. In Swift 2, the compiler would implicitly unwrap id (and the other properties) in more places than it will in Swift 3. Since string interpolation ("\(...)") can accept any type, the Swift 3 compiler passes id as Int? instead of implicitly unwrapping it.



Related Topics



Leave a reply



Submit