Hi all!
(If you're familiar with the problem of custom variance annotations in generics, you might want to skip this introduction.)
I've been developing a system in which I need to pass arrays by reference. To do that, I implemented a class called ArrayClass
that wraps a normal Array
. Here's a simplified version of it:
public final class ArrayClass<Element> {
public var array: [Element]
public init(array: [Element]) {
self.array = array
}
}
I often run into the problem of having to cast ArrayClass
es, for instance from ArrayClass<Int>
to ArrayClass<Any>
. To do that, I created an initializer:
public init<T>(arrayClass: ArrayClass<T>) {
self.array = arrayClass.array as! [Element]
}
However, this initializer hits on the problem that is the lack of covariance in generics, and has to circumvent it with the force cast. For example:
let intArray = ArrayClass<Int>(array: [1, 2, 3])
// Compiles and works ok
let anyArray = ArrayClass<Any>(arrayClass: intArray)
// Compiles fine (unfortunately) and only crashes at runtime
let stringArray = ArrayClass<String>(arrayClass: intArray)
This was just to illustrate the problem. I've read many past discussions on adding custom variance to the language, and they seem to center on two things (with which I agree):
- Variance would probably be a complex thing to add to the language and there hasn't been much empirical evidence that it would be worth it.
- Most use cases seem to center on writing collections, and there might be an easier way to do that.
I would like to focus on what would be an easier way to add some type safety here. Some people on previous discussions have suggested making some of the compiler magic used on arrays public, but I don't really understand what that would entail.
My tentative suggestion: for this use case, it would be enough to allow something like this:
public init<T>(arrayClass: ArrayClass<T>) where T: Element {
self.array = arrayClass.array as! [Element]
}
This where
clause, in my head, would mean "only accept this function call if T
is a subtype of Element
". If that worked, everything else seems to fall into place. However, in the type-system, this where
clause seems to mean something like "constrain T
to the protocol or class Element
", because I get this error:
// error: type 'T' constrained to non-protocol, non-class type 'Element'
So, yeah. Does this suggestion (of allowing such a where
clause) make sense? Am I overlooking something really obvious and complicated? Does anyone have another suggestion?