Swift Operators and Nil

Nil coalescing operator for dictionary

Trying use like yours give you and error :
Left side of mutating operator has immutable type '[Int]?'

By putting parentheses it will be no compile error and it will work

var myDict = [String : [Int]]()
myDict["Algebra"]?.append(contentsOf: [98,78,83,92]) ?? (myDict["Algebra"] = [98,78,83,92])
print(myDict) // ["Algebra": [98, 78, 83, 92]]

Swift Documentation is here for Infix Operators.

What is the role of the nil-coalescing operator ?? in optional chaining?

That's not the nil-coalescing operator you are seeing. The nil-coalescing operator is a binary operator, that needs to be placed between an Optional value and a non-Optional one or two Optional values. In your code example, that's clearly not the case.

What you are seeing is optional chaining on a nested-Optional.

let nestedOptional: Int?? = nil

nestedOptional??.description

You can create Optional values whose wrapped value is also an Optional, in which case you need to unwrap each optional layer one-by-one.

Using nil-coalescing operator with try? for function that throws and returns optional

Essentially, this has to do with the grammar of the try operator. When used with a binary expression without brackets, try applies to the whole binary expression, so this:

try? machine.itemCode(code: 0) ?? "Unknown"

is the same as:

try? (machine.itemCode(code: 0) ?? "Unknown")

Since itemCode throws an error, the latter part of the expression ?? "Unknown is ignored, and the try? expression evaluates to nil.

On the other hand, the second expression is like this:

(try? machine.itemCode(code: 0)) ?? "Unknown"

The try? expression is evaluated first (to nil), then the ?? is applied, evaluating the whole expression to "Unknown".

Nil-Coalescing Operator without changing value

You can design your own infix operator:

infix operator ?=
func ?=<T>(lhs: inout T, rhs: T?) {
lhs = rhs ?? lhs
}

var a = "a"
var b: String? = "b"

a ?= b

print(a) // "b\n"

Nil Coalescing Operator with mixed types

You can write some thing like this:

let statusCodeString = statusCode?.description ?? "None"

Or if you want to work with some types which does not have the property description:

let statusCodeString = statusCode.map{String(describing: $0)} ?? "None"

Why is the nil coalescing operator ?? returning nil?

To see what is going on here, assign the dictionary look up to a constant:

let name = mysteryInc["Scooby-Doo"]
print(type(of: name))

Output:

Optional<Optional<String>>

So, name is a double Optional, a String??.

When the nil coalescing operator is applied, it unwraps the outer Optional and leaves an Optional<String> (aka String?). In the example in the question, Swift treats the String literal "no last name" as type String? so that ?? can be applied.

If you examine the value of name you will find that it is Optional(nil). All dictionary look ups return Optionals because the key might not exist (in which case they return nil). In this case, the mysteryInc dictionary is of type [String: String?]. The value corresponding to "Scooby-Doo" is nil which then gets wrapped into an Optional (because of the dictionary look up) to become Optional(nil).

Finally, the value Optional(nil) is unwrapped by ??. Since Optional(nil) != nil, Swift unwraps Optional(nil) and returns nil instead of returning the expected "no last name".

And that is how you can get nil from the nil coalescing operator. It did unwrap the Optional; there just happened to be a nil inside of that Optional.


As @LeoDabus noted in the comments, the correct way to deal with this situation is to conditionally cast the name to String before applying the nil coalescing operator:

let lastname = mysteryInc["Scooby-Doo"] as? String ?? "no last name"

In reality, it is best to avoid having Optionals as values for your dictionary for the very reason that @Sulthan raises:

What does mysteryInc["Fred"] = nil do?

Assigning nil to a dictionary entry removes that entry from the dictionary, so mysteryInc["Fred"] = nil doesn't replace Optional("Jones") with nil, it removes the "Fred" entry altogether. To leave "Fred" in the dictionary and make his last name nil, you need to assign mysteryInc["Fred"] = Optional(nil) or mysteryInc.updateValue(nil, forKey: "Fred").

Swift 4.2, Xcode 10.2 nil coalescing operator warning

As mentioned by OOPer, the solution is to provide a default Any value as right hand side of the operator, in this case an [Any], because the NSOrderedSet has no specific type bound to it. The solution is:

return [
"sublevels": (self.sublevels?.array ?? []) as NSObject
]

For more info on this matter I suggest you take a look at type casting. At the bottom of the page there is an explanation about casting the Any type.

why multiple optionals(wrap nil) compare with operator == return false?

There are four kinds of values that a Int??? can have:

  1. .none (nil)
  2. .some(.none) (a non nil Int??? wrapping a nil Int??)
  3. .some(.some(.none)) (a non nil Int??? wrapping a non nil Int?? wrapping a nil Int?)
  4. .some(.some(.some(n))) where n is an Int (an Int wrapped by 3 layers of Optionals)

Similarly, there are three kinds of values that an Int?? can have

  1. .none
  2. .some(.none)
  3. .some(.some(n)) where n is an Int

When you write nil, it always means .none of whatever type that context needs, so both opt1 and opt2 are .none here.

What happens when you pass them to the == operator? Well, After some overload resolution/type inference, the compiler finds that == takes a two Int??? parameters, but you have passed an Int?? as the second argument. Luckily, there exists a conversion from any value t of type T to type T? - .some(t).

So after being passed into the ==, opt2 changes from .none to .some(.none), as you can see from this code snippet:

func test(lhs: Int???, rhs: Int???) {
if case .none = lhs, case .some(.none) = rhs {
print("lhs is .none and rhs is .some(.none)")
}
}

test(lhs: opt1, rhs: opt2) // prints

Hence they are "not equal".

The debugger seems to be showing both .none and .some(.none) as "nil", possibly because both of their debugDescription/description is "nil".


If you don't care about the multiple layers of optionals, you can just unwrap them to a single layer by doing as? Int, then compare:

print((opt1 as? Int) == (opt2 as? Int)) // true


Related Topics



Leave a reply



Submit