Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't Swift nil-coalescing ternary operator return unwrapped type?

I read that the ternary operator ?? unwraps an optional if it is not nil, but if I do:

var type: String?
type = "milk"
let certainType = type ?? "melon"

then certainType will still be a String?, and if I do

println("it's a \(certainType)")

it will print:

it's a Optional("milk")

Thoughts?

Update:

Sorry for the confusion - I meant var type: String?

I get that it should print "it's a milk", but what I saw in console is a "it's a Optional("milk")" - anyone else experienced the same issue? Could that be caused by string interpolation?

Asked by @Antonio, here is more context and real code and logging snapshot - type is from Note, which is a NSManagedObject class used to deal with xcdatamodel

class Note: NSManagedObject {
  @NSManaged var type: String?
}

And I have the type set to 'todo' at some point, then have the following code to print them out:

println("type class:\(_stdlib_getDemangledTypeName(note.type))")
let type1 = note.type ?? "note"
println("type1:\(type1)")
let type2: String = note.type ?? "note"
println("type2:\(type2)")

And the output:

type class:Swift.Optional
type1:Optional("todo")
type2:todo

As you can see, if I don't explicitly mark the type1's type as String, it will print undesired result Optional("todo") - I use the type in a string interpolation to construct a path so it matters

like image 378
hyouuu Avatar asked Jan 31 '15 21:01

hyouuu


People also ask

What is nil coalescing operator Swift?

Swift's nil coalescing operator helps you solve this problem by either unwrapping an optional if it has a value, or providing a default if the optional is empty. Because name is an optional string, we need to unwrap it safely to ensure it has a meaningful value.

What is nil coalescing & ternary operator?

The nil-coalescing and ternary conditional operators are what's known as syntatic sugar. They sugar-coat verbose code in more concise code, and make your Swift code more readable.

What is optional chaining what is nil coalescing operator?

Optional chaining is a process for querying and calling properties, methods, and subscripts on an optional that might currently be nil . If the optional contains a value, the property, method, or subscript call succeeds; if the optional is nil , the property, method, or subscript call returns nil .

What does += mean in Swift?

Like C, Swift provides compound assignment operators that combine assignment ( = ) with another operation. One example is the addition assignment operator ( += ): var a = 1. a += 2. // a is now equal to 3.


5 Answers

I can't prove what I am going to say, so any feedback is very well appreciated.

The OP asserts that code similar to this:

var type: String? = "milk"
let certainType = type ?? "melon"
println("it's a \(certainType)")

prints an unexpected string:

"it's a Optional("milk")"

whereas it should be:

"it's a milk"

It turns out that happens when the variable is actually a property with the @NSManaged attribute.

I suspect that there is a bug in type inference. The OP states that:

let certainType = type ?? "melon"

prints the wrong result, whereas:

let certainType: String = type ?? "melon"

prints the correct one.

So for some reason, without explicitly indicating the variable type, the nil coalescing operator is returning an optional.

If I change the type of the type variable to either AnyObject or AnyObject?, it actually prints the unexpected result:

var type: AnyObject = "milk"
let certainType = type ?? "melon"
println("it's a \(certainType)")

"it's a Optional(milk)"

My guess is: because the @NSManaged attribute is used, the property is inferred with the wrong type (AnyObject?) when used, unless the correct type is explicitly indicated.

As to why that happens, no idea (besides thinking it's a bug)

Feel free to up or down vote this answer, and most important don't consider it as a solution - I would appreciate feedback though, because I'm curios to know what's going on and whether it's really a bug or not.

like image 162
Antonio Avatar answered Oct 25 '22 00:10

Antonio


This is (I think) by design. If you do this:

let type = "mil"
println("it's a \(type)")

If I am not mistaken, this will print it's a "milk" which is far more useful when debugging than it's a String. Note that \(...) is similar to %@ in Objective-C: classes can override their string representation.

Also, as Antonio already pointed out:

  1. You cannot change an immutable variable (i.e. a let).
  2. Optional let variables therefore are useless (i.e. don't do let type: String? = "milk" because it clearly is never going to be nil).
like image 25
cutsoy Avatar answered Oct 24 '22 23:10

cutsoy


I read that the ternary operator ?? unwraps an optional if it is not nil, but...

It does unwrap it.

let type: String? type = "milk" let certainType = type ?? "melon"

Your code won't compile, but if you change the let to var, it will work and certainType will be of type String.

var type: String?
type = "milk"
let certainType = type ?? "melon"

println("it's a \(certainType)") // prints "it's a milk"
like image 39
MirekE Avatar answered Oct 24 '22 23:10

MirekE


This is known as the Nil Coalescing Operator.

If you have an Optional a, the result of a ?? b will be a! if a is not nil and b if it is. It's shorthand for this expression:

a != nil ? a! : b

So, you are using the operator correctly, but you must initialize a constant on the same line that you declare it:

let type: String? = "milk"

However, I'm assuming that you don't want that to be a constant in this context (since you're checking what it is), so if you use var to declare it, your existing code should work.

like image 26
AstroCB Avatar answered Oct 25 '22 00:10

AstroCB


I was having a similar problem.

I have a dictionary of type [String : AnyObject], but the entry I was after turns out to be a String value. Fearing it may not always be present. I was retrieving it with the following code:

let message = dictionary["message"] ?? "No message"

However, when the entry is present, that would still give the string:

Optional(the actual message)

i.e., the variable message is of type String?.

I changed it to:

let message = (dictionary["message"] as? String) ?? "No message"

...and now the retrieved value is an unwrapped, non-optional string.

like image 38
Nicolas Miari Avatar answered Oct 25 '22 00:10

Nicolas Miari