i am trying to express the following "type erased" structs in swift, but getting the Cast from 'Some<Int>' to unrelated type 'Some<Any>' always fails warning and it crashes indeed. how to do this right and what is the reason for Some<Int> not being type compatible with Some<Any>?
struct Some<T> {
var v: T
}
let someInt = Some(v: 0)
let someString = Some(v: "hello")
let i = someInt as! Some<Any> // crashes
let s = someString as! Some<Any> // crashes
let someThings: [Some<Any>] = [i, s]
Generic parameters are invariant w.r.t. their containers. So Some<Int> is unrelated to Some<Any>*. You'd need to reconstruct new Some with type-erased T.
Some(v: someInt.v as Any)
* There're some exceptions baked in to the compilers, e.g. Array, Dictionary.
extension Some {
func upcasted() -> Some<Any> { Some<Any>(v: v) }
}
extension Some where T == Any {
func upcasted() -> Some<Any> { self }
func downcast<U>(to targetType: U.Type) -> Some<U>? {
guard let v = v as? U else { return nil }
return Some<U>(v: v)
}
}
let someInt = Some(v: 0)
let someString = Some(v: "hello")
let i = someInt.upcasted() // works
let s = someString.upcasted() // works
let someThings: [Some<Any>] = [i, s]
print(someThings.map { $0.v }) // works
print(someThings[0].downcast(to: Int.self)!.v) // works
print(someThings[1].downcast(to: String.self)!.v) // works
Depending on what you do with T, it doesn't make sense in general. If you only have a getter of T:
extension Some {
var value: T { ... }
}
then you can't convert Some<Any> to Some<Int> because value could return something other than Int. OTOH, if you have a setter:
extension Some {
func set(value: T) { ... }
}
then you can't convert Some<Int> to Some<Any> because value because you could supply "some string" to Some<Any>.set, but you can't do that to Some<Int>.set.
In general, generic parameter is invariant w.r.t. to its container. You'd need a much stronger information to have Some<Any> be subtype or supertype (or both) of Some<Any>, which isn't possible to express in Swift right now (though is theoretically possible).
Even if you make sure that one is a variant of another, the type layout of Some<Any> and Some<Int> isn't guaranteed to be compatible, so you can't just reinterpret that portion memory as different generic. You'd need to do the conversion (again, not available in Swift).
your examples are understandable, but i wouldn't want anything silly, like treating Some<Int> the same way as Some<String> via the intermediate typecast to Some<Any>. naturally the inappropriate calls to getters / setters in your above example would crash and that's expectable. i was only after the roundtrip Some<T> -> Some<Any> -> Some<same T>, obviously if i tried to cast it to Some<different Type> i would expect it not to work correctly.
true. although they managed to do that [Int] is type related to [Any]...
let intArray: [Int] = [1, 2, 3]
let anyArray: [Any] = intArray
let intArray2: [Int] = anyArray as! [Int]
let doubleArray: [Double] = anyArray as! [Double] // expected crash
and this is actually:
let intArray: Array<Int> = ...
let anyArray: Array<Any> = intArray
let intArray2: Array<Int> = anyArray as! Array<Int>
let doubleArray: Array<Double> = anyArray as! Array<Double> // expected crash
so the question is, what special "Array" has that my "Some" type doesn't, is that baked into the compiler?
I mean, with the current type system, you can't express the relationship between Some<Int> and Some<Any>. The former could be subtype of the latter, the latter could be subtype of the former, but having both would be "silly". If you just want a round-trip, you could just cast the entire Some<Int> -> Any -> Some<Int>.
Technically they aren't. They just make a copy (and convert each element) every time you do the casting. It is possible to expand such capabilities to custom types, but AFAIK that area hasn't been explored yet. Unexpectedly, mere type casting isn't exactly a trivial task.