Function-local generic constraints/casting

I recently ran into a situation where I had a generic type with a protocol constraint, and I needed to dynamically convert it to a generic type with a different constraint if possible. I believe there is sufficient metadata that this could be done at runtime, but I could not figure out how to express it.

Example:

// API consumers can only see this function. I do not control the API,
// so I can’t expose my fastPath() function
public func encode<T>(_ value: T) throws where T: Encodable {
    If let v = value as? T: MyProtocol {
        fastPath(v)
    } else {
        // ...
    }
}
internal func fastPath<T>(_ value: T) where T: MyProtocol {
    // ...
}

For reasons, my fastPath function can’t accept an existential, it has to be generic (otherwise this would be easy).
I need a way to dynamically convert from a concrete type T: Encodable to a concrete type T: MyProtocol.

It sounds like you want dynamic dispatch to a non-existential protocol which is not part of the static type information at the call site.

There is, technically, a way to do this. It’s not pretty, it’s not obvious, and it wasn’t designed intentionally, but it does exist.

See the links in this comment for details. No guarantees about runtime performance though.

Can you specialise your encode function? In this case, you could do

public func encode<T>(_ value: T) throws where T: MyProtocol, T: Encodable {
        fastPath(v)
}

public func encode<T>(_ value: T) throws where T: Encodable {
        // ...
}

Right, I'm not expecting this to work statically because there is not enough information at the call site. I thought that at runtime the list of protocols a type conforms to was dynamically knowable, so it should be possible to cast a T: Foo to a T: Bar if the underlying type T conforms to both Foo and Bar even if the Bar conformance is not statically known at the call site.

Thank you for the link! I will read up on it, but it's not something I can understand at a glance :cold_sweat:

Unfortunately function specializations won't work in this case because this is inside the implementation of an UnkeyedEncodingContainer. The Encoder API vends an existential of UnkeyedEncodingContainer so my specializations could never be called because they are not part of the protocol.

I could dynamically cast the container existential in all of my implementations of encode(to:) to check if it's this one, but that won't work for Encodable types that I don't control such as Array, etc.

Side note: if we ever re-do Codable it would be nice if it used opaque types instead of existentials.

Terms of Service

Privacy Policy

Cookie Policy