Generic Covariance to improve API Usage

Is this the right moment to try to introduce a generalized Generic Covariance, or this has been discarded?
As far as I know, only Arrays support Generic Covariance. Is it possible to generalize it or to introduce in core elements like Result and allow libraries like Combine to use it?

class Base {
}
class Derived: Base {
}

class ArrayBase {
    func foo(x: [Derived]) -> [Base] { return x }
}

Would allow simplification of the following code instead of throwing errors and require casting. Somethings that's already done today for Optionals and Array.

class JustBase {
    func foo(x: Just<Derived>) -> Just<Base> { return x } // Compiler Error: Cannot convert return expression of type 'Just<Derived>' to return type 'Just<Base>'
}


class ResultBase {
    func foo(x: Result<Derived, Error>) -> Result<Base, Error> { return x } // Compiler Error: Cannot convert return expression of type 'Result<Derived, Error>' to return type 'Result<Base, Error>'
}
10 Likes

It's a useful feature but also really really hard.

  • For value types with a covariant generic parameter, the upcast might involve a representation change (consider going from Optional<Int> to Optional<Any>).

  • For reference types with a covariant generic parameter, you don't even have a chance to change the representation (since you need to preserve reference semantics), which rules out certain kinds of covariance.

  • For non-ObjC generic reference types, the generic arguments are included in the runtime type, which means the same instance can't be both a Box<Dog> and a Box<Animal> at the same time. One of them is always the "real" type.

  • If you're implementing something like Array, you have to be doubly careful about having a reference type backing your value type. Make sure not to put a Cat in your Animal array storage if it's also backing an array of Dogs!

  • And none of that is talking about the compiler side of it, expanding the limited support that's there for four specific types (Array, Set, Dictionary, and Optional) to something generalizable.

So it's theoretically possible to add to Swift for value types*, it'd be a big undertaking on the compiler side, and probably not so straightforward to write a type for it even once the feature is fully designed.

* and (ironically) for Objective-C classes, which do not store the generic parameters at runtime.

14 Likes

The value type like Optional and Array as today have this handle at SIL level. And I understand that we can handle them as they are part of the standard library and we have implemented the covariance of optional during devirtualizing.

class OptionalVariant {
    func foo(x: Int?) -> Any? { return x }
}

I'm thinking, as we have it for Optional today if we could generalize it and check value types have initializers that match the covariance requirement. In order to apply at SIL Generation.

For reference type, It's not clear to me how the UpCasting can be done, but I will keep investigating.

For something like Array, I suppose that a restriction like immutability would be required to avoid putting a Pear in a Bag upcast to Bag.

API and usage wise it's maybe another route to explore to have a similar solution like. Like the ability to box the generic type.

Bag<any Fruit>