Currently (in Swift 4.2.1), it is an error to cast a value from Any to Optional, even when that value was created as the exact same Optional type and previously cast to Any. Here is an example:
let x: Int? = 1
let y: Any = x as Any
print(type(of: y)) // "Optional<Int>\n"
let z: Int? = y as! Int? // error: cannot downcast from 'Any' to a more optional type 'Int?'
Is this a bug, or an oversight, or an intentional design decision? In particular, would it require an evolution proposal to make it possible for this to work? My expectation was that Any could wrap any type, and be unwrapped back to that type.
So far the only way I have found to extract the original Optional value, is via a generic helper function such as:
func unwrap<T>(_ x: Any) -> T {
return x as! T
}
let z: Int? = unwrap(y)
print(type(of: z)) // "Optional<Int>\n"
It seems rather peculiar that we can attempt to unwrap an Any into an unspecified generic type, but we are prohibited from unwrapping an Any into the exact specific type we know it contains, if that type is Optional.
I agree that this feels like an arbitrary limitation, especially given that this works exactly as expected:
let maybeInt = 1 as Int?
let anything = maybeInt as Any
let opened = anything as? Int?
// error: cannot downcast from 'Any' to a more optional type 'Int?'
func dynamicCast<T>(_ value: Any, to _: T.Type) -> T? {
if let value = value as? T {
return value
} else {
return nil
}
}
if let opened = dynamicCast(anything, to: Int?.self) {
print("got an optional Int: \(opened)")
} else {
print("it's something else entirely")
}
I think we added this to stop people from expecting x as! Int? to act like x as? Int. It's not really a correct error when casting from any (non-class) existential / protocol value (Any is "no protocols").
Could you elaborate? I run into the error described here multiple times, but I've never tried to do x as! Int? instead of x as? Int. Why would someone think that they're the same?
:-/ I don't have much here, but I remember it coming up in the first few years of Swift. It's also logic shared with x as? Int?, which is a more reasonable typo to make (because you want the result type to be optional). The compiler could certainly make a different decision about diagnosis based on as? vs. as!.
There is no light-weight built-in way of doing this, but a common approach that just might meet your criteria for convolution is to use a custom protocol (often scoped internal or even fileprivate).
I too just ran into this error trying to cast Any as an optional and would have liked for it to work, so I just wanted to add my vote for allowing casting from Any to Optional<SomeType> if there's no harm in it.
To be more specific, what I want is that when I have a value of type Any I can distinguish between nil of different types:
let nilInt: Int? = nil
let nilString: String? = nil
func isNilString (_ value: Any) -> Bool {
guard let optionalString = value as? String? else { return false }
return optionalString == nil
}
isNilString(nilInt) // Should return false
isNilString(nilString) // Should return true
You can always add a failable initializer to Optional that does the conversion:
let x: Int? = 1
let y: Any = x as Any
print(type(of: y)) // "Optional<Int>\n"
extension Optional {
init?(from any: Any) {
guard let opt = any as Any? as? Self else {
return nil
}
self = opt
}
}
let z: Int? = Optional(from: y)!