GiumaSoft
(Giuseppe Mazzilli)
1
I can't figure how that could be right in Swift playground
Case (1)
var optional: String? // nil
var any: Any = optional // nil
debugPrint( optional == nil ? "true" : "false" ) // print "true"
debugPrint( any == nil ? "true" : "false" ) // print "false"
Case (2)
var optional: String? // nil
var any: Any? = optional // nil
debugPrint( optional == nil ? "true" : "false" ) // print "true"
debugPrint( any == nil ? "true" : "false" ) // print "true"
Case (3)
var any: Any = Optional<String>.none
debugPrint( any == nil ? "true" : "false" ) // print "false"
debugPrint( Optional<String>.none == nil ? "true" : "false" )
// print "true"
Is there any way to check if Any property is nil?
itaiferber
(Itai Ferber)
2
What you're seeing here is a result of "Optional promotion" in Swift: if a function takes a value of type T? and you try to pass a value of type T to it, Swift will implicitly wrap your value in .some in order to convert it to the right type. This includes static func ==(...).
Although Any is allowed to contain a nil value despite not being an Optional, Swift's Optional promotion rules still apply to it; this means that when you write any == nil, Swift still promotes any: Any to .some(any): Any? in order to compare against nil. In this case, .some(any) is not nil, hence the false results.
If you try to compile this code outside of a playground, you'll get warnings letting you know that this is about to happen:
/main.swift:2:16: warning: expression implicitly coerced from 'String?' to 'Any'
var any: Any = optional // nil
^~~~~~~~
/main.swift:2:16: note: provide a default value to avoid this warning
var any: Any = optional // nil
^~~~~~~~
?? <#default value#>
/main.swift:2:16: note: force-unwrap the value to avoid this warning
var any: Any = optional // nil
^~~~~~~~
!
/main.swift:2:16: note: explicitly cast to 'Any' with 'as Any' to silence this warning
var any: Any = optional // nil
^~~~~~~~
as Any
/main.swift:5:17: warning: comparing non-optional value of type 'Any' to 'nil' always returns false
debugPrint( any == nil ? "true" : "false" ) // print "false"
~~~ ^ ~~~
One way to get the Optionality back is to force-cast any to an Optional:
debugPrint(any as! Any? == nil ? "true" : "false") // "true"
Unfortunately, you will get an unsilenceable warning informing you that this cast will always succeed.
See Any should never be nil and similar threads here on the forums for more info.
In general, because of this, if you know that your value may be Optional, it's significantly more ergonomic to work with Any? over Any.
2 Likes
tera
3
Note that you have a warning here:
var any: Any = optional // nil
// 🔶 Warning: Expression implicitly coerced from 'String?' to 'Any'
This could do as a test:
if let v = any as? Optional<Int> {
if let v {
print("false") // can't happen if there's a type mismatch
} else {
print("true")
}
} else {
print("false")
}
GiumaSoft
(Giuseppe Mazzilli)
4
Investigating further I discovered that:
If case Optional<Any>.none = any {
print(“true”)
}
do the job. Is that right form?
GiumaSoft
(Giuseppe Mazzilli)
5
@itaiferber I agree with your explaination, Can u explain me the behaviour in 2nd case?
var optional: String?
var any: Any = optional
print(any) // nil
if case Optional<Any>.none = nil { //by "Optional Promotion" compare is translated into ` case Optional<Any>.none = Optional<Any>.none `
print("true") // print "true"
} else {
print("false")
}
if case Optional<Any>.none = any { // by "Optional Promotion" compare is translated into ` case Optional<Any>.none = Optional<Any>.some(any) `
print("true") // print "true" -----> Why?
} else {
print("false")
}
if Optional<Any>.some(any) == nil { // by "Optional Promotion" compare is translated into ` case Optional<Any>.some(any) = Optional<Any>.none `
print("true")
} else {
print("false") // print "false"
}
if case Optional<Any>.none = Optional<Any>.some(any) { // We already explicitated both as Optional so there no promotion
print("true")
} else {
print("false") // print "false"
}
GiumaSoft
(Giuseppe Mazzilli)
6
U already unwrapped "v" on first conditional check which is the reason of the 2nd if?
itaiferber
(Itai Ferber)
7
This is a great question! When you write if case _ = _, you are actually pattern matching the two sides of the = together to "destructure" the right-hand-side into the left-hand-side. This happens dynamically, and here, optional promotion actually does not happen unless it's absolutely necessary, in order to keep the pattern matching consistent.
For example, you can write code like
let x = 5
if case Optional<Any>.none = x {
print("nil??")
} else {
print("Not nil")
}
but you'll get a warning:
/main.swift:2:4: warning: non-optional expression of type 'Any' used in a check for optionals
if case Optional<Any>.none = x {
^ ~
The compiler can promote x from Int to Int?, but it knows the condition will always fail. However, if you explicitly type x: Any = 5, the warning goes away (as expected), because the value of x will be checked dynamically at runtime.
So, to annotate your cases:
case Optional<Any>.none = nil: not actually optional promotion; nil is just Optional<_>.none, so this is equivalent to writing case Optional<Any>.none = Optional<Any>.none, which matches
case Optional<Any>.none = any: not optional promotion, but dynamically matching the value inside any to Optional<Any>.none
Optional<Any>.some(any) == nil: like (1), actually not optional promotion; nil is just Optional<_>.none, so this is equivalent to Optional<Any>.some(any) == Optional<Any>.none
case Optional<Any>.none = Optional<Any>.some(any): like you say, no promotion here; this is just a direct mismatch
If you really need to store an optional value inside of Any, this would actually likely be the preferred approach rather than the as! cast I suggested above.
2 Likes