Generalization of Implicit Conversions

To more formally pitch a solution I proposed in another thread:

Proposed Solution

I propose that we should allow types to inherit from other arbitrary types at the point of their definition in the same way that we declare subclassing or protocol conformance, and then require that the type provide a func upcast() -> SuperType member function.

To give an example, UnsafeMutablePointer would be declared as something like:

struct UnsafeMutablePointer<Pointee>: @subtypeConvertible UnsafePointer<Pointee> {
    public func upcast() -> UnsafePointer<Pointee> {
        return UnsafePointer(self)
    }
}

The subtype would inherit all protocol conformance requirements from the inherited type, and would have default implementations for the requirements that upcast to the inherited type and call the protocol requirement on that.

Upcasts can be chained, so if C: @subtypeConvertible B and B: @subtypeConvertible A:

let a: A = C()

is implemented as:

let a: A = (C().upcast() as B).upcast() as A

If there's a way to make the runtime casting functionality work, we should also allow conditional downcasts using the as? operator if the subtype provides a init?(downcasting: SuperType) initialiser:

enum GPUResource {
    case texture(GPUTexture)
}

struct GPUTexture : @subtypeConvertible GPUResource {
    public func upcast() -> GPUResource {
        return .texture(self)
    }
 
    public init?(downcasting superType: GPUResource) {
        switch superType { 
        case .texture(let texture):
            self = texture
        default:
            return nil
        }
    }
}

This proposed solution unfortunately doesn't cleanly subsume any of the currently supported implicit conversions. Implicitly, we have for any type T that T: @subtypeConvertible Optional<T>, but it's not reasonable to ask the user to write that out for every type.

For the collection conversions, we could maybe have something like:

struct Array<Element>: @subtypeConvertible Array<Super> where Element: Super {}

To @Ben_Cohen's point above, I'm not sure whether supporting that is a good idea or not.

If we want to support widening integer conversions, those could be written as:

struct UInt16: @subtypeConvertible UInt8 {}

Motivating Use Case

For performance reasons, I've on multiple occasions implemented my own protocol existential equivalent. One example of this: in a library I maintain, Resources are handle types that index into struct-of-array registries. To give a simplified implementation:

enum ResourceType: UInt8 {
    case buffer
    case texture
    case heap
}

struct Resource {
    let handle: UInt64

    var type: ResourceType { 
        return ResourceType(rawValue: UInt8(self.handle.bits(in: 56..<64))) 
    }
}

struct Texture /*: @subtypeConvertible Resource */ {
    let handle: UInt64

    init?(_ resource: Resource) {
        guard resource.type == .texture else { return nil }
        self.handle = resource.handle
    }
}

Currently, I have a parent ResourceProtocol protocol which all conversions have to go through but which I never want to use as an existential; this proposal would allow me to remove that.

In particular, if we supported as? downcasts, it would have prevented a number of bugs where I've inadvertently written:

let resource: Resource = ...
if let texture = resource as? Texture {} // never succeeds
// as opposed to what's currently required:
if let texture = Texture(resource) {}