Implementing Any vs Any?

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?

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

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")
}

Investigating further I discovered that:

If case Optional<Any>.none = any {
    print(“true”)   
}

do the job. Is that right form?

@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"
}

U already unwrapped "v" on first conditional check which is the reason of the 2nd if?

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:

  1. 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
  2. case Optional<Any>.none = any: not optional promotion, but dynamically matching the value inside any to Optional<Any>.none
  3. 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
  4. 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