Is type of `Optional<T>.none` the same for any T by design?

let optionalString: String? = nil
if let boolValue = optionalString as? Bool? {
    print("\(type(of: boolValue))")
}

This code prints out

Optional<Bool>

It means that Optional<Bool>.none and Optional<String>.none are the same thing from type system perspective. Why? Is it by design?

5 Likes

Well not quite the same, you still need a coercion. Interesting observation none-the-less.

This program will print Optional<Bool>:

if let a = Optional<String>.none as? Optional<Bool> {
  print(type(of: a))
}

Can we (by making MyOptional more "complete") make the following program similarly print MyOptional<Bool>:

enum MyOptional<Wrapped> {
  case none
  case some(Wrapped)
}

if let a = MyOptional<String>.none as? MyOptional<Bool> {
  print(type(of: a))
}

?
(If so, how? If not, why not?)

2 Likes

Yes, this is deliberate, and yes, this is special-cased to Optional. The motivation comes from code like this:

let obj: Superclass? = SubclassA()
let anyObj: AnyObject? = obj
let castedSuper = anyObj as! Superclass? // should succeed
let castedSubA = anyObj as! SubclassA? // should succeed
let castedSubB = anyObj as! SubclassB? // should fail

let obj: Superclass? = nil
let anyObj: AnyObject? = obj
let castedSuper = anyObj as! Superclass? // should succeed
let castedSubA = anyObj as! SubclassA? // should succeed
let castedSubB = anyObj as! SubclassB? // should succeed

Because AnyObject? doesn't contain any information about the original type, a nil value has to be treated as valid for any class type. While some type checking could go into Any (which does record the type it was created with), that would make Any and AnyObject behave differently, which would be rather sketchy. Allowing nil to convert to any Optional type through a dynamic cast was the most consistent answer.

(In a language that doesn't allow casting up and down a class hierarchy, you wouldn't need to support this kind of thing. The feature needed to apply this to user-defined types would be a form of variance, saying that any MyOptional<Subclass> is a valid instance of MyOptional<Superclass>. In Swift, variance is hard-coded for Optional, Array, Dictionary, and Set; allowing it for arbitrary types has a lot of complications. There's discussion of that elsewhere on the site.)

8 Likes

This phenomenon also applies to empty collections. Example from @codafi's tweet.

class Animal {}
class Cat: Animal {}
class Dog: Animal {}

print(Dog() is Cat)     // false
print(Cat() is Dog)     // false
print([Cat]() is [Dog]) // true
print([Dog]() is [Cat]) // true
5 Likes