J. Cocoe
J. Cocoe

Reputation: 941

Why is Swift nil-coalescing returning an Optional?

First, I try mapping a [String?], to get a [String]:

$ xcrun swift
Welcome to Apple Swift version 2.2 (swiftlang-703.0.18.8 clang-703.0.30). Type :help for assistance.
  1> import Foundation
  2> let j: [String?] = ["a", nil]
j: [String?] = 2 values {
  [0] = "a"
  [1] = nil
}
  3> j.map {$0 ?? ""}
$R0: [String] = 2 values {
  [0] = "a"
  [1] = ""
}

This makes perfect sense to me. I nil-coalesce a String?, and I get a String. But with [AnyObject?], something strange occurs:

  4> let k: [AnyObject?] = ["a", nil]
k: [AnyObject?] = 2 values {
  [0] = "a"
  [1] = nil
}
  5> k.map {$0 ?? ""}
$R1: [AnyObject?] = 2 values {
  [0] = "a"
  [1] = (instance_type = 0x00007fff7bc2c140 @"")
}

I'm nil-coalescing optionals, but this time I get out an optional. Why?

The Swift Programming Language says a ?? b is shorthand for a != nil ? a! : b, but when I try that, I get out an array of non-optionals:

  6> k.map {$0 != nil ? $0! : ""}
$R2: [AnyObject] = 2 values {
  [0] = "a"
  [1] = ""
}

Am I misunderstanding how ?? is supposed to work? What is going on here?

Upvotes: 7

Views: 402

Answers (2)

J. Cocoe
J. Cocoe

Reputation: 941

It has come to my attention that Apple considered this a bug in Swift 2.

In Swift 3, the 1st example above still works, while the 2nd and 3rd examples are invalid syntax (with or without Foundation bridging).

Replacing the AnyObject declaration with Any works: a ?? b then behaves identically to a != nil ? a! : b, as the documentation says.

Upvotes: 0

OOPer
OOPer

Reputation: 47896

The detailed behaviour is not well-documented, so, would change in the future Swifts.

But you should know coalescing operator has two overloads:

@warn_unused_result
public func ??<T>(optional: T?, @autoclosure defaultValue: () throws -> T) rethrows -> T

@warn_unused_result
public func ??<T>(optional: T?, @autoclosure defaultValue: () throws -> T?) rethrows -> T?

In your case, Swift has chosen the latter for your code.

You can test with a simplified codes like:

let x: AnyObject? = "a"
x ?? ""

The inferred type (in Swift 2.2.1) becomes AnyObject?. But this code is also valid.

let y: AnyObject = x ?? ""

String literals like "" can be treated as variety of types. All of these are valid in Swift.

"" as String
"" as String?
"" as NSString
"" as NSString?
"" as AnyObject
"" as AnyObject?

So, with some unspecified reason Swift has chosen AnyObject?. And, in case type inference can be ambiguous, you should use explicit type annotation, as suggested in appzYourLife's comment.

Upvotes: 2

Related Questions