Type casting to opaque concrete type?

I want to constant fold some fast paths by type casting a value to some opaque concrete type. I hoped this would work, but it does not compile:

init<T>(_ value: T) {
    //  this does not compile and `any` is too slow
    if  let value = value as? some FastPathCompatibleValue {
        self.init(fast: value)
    } else {
        self.init(slow: value)
    }
}

Am I missing something? Or is it not supported? In the latter case, I would love to see it added. I mean, it's there, just not the concrete version.

Typically the way you would do this is to use your FastPathCompatibleValue protocol as a simple marker protocol for the compiler. This way, you can split up your initialiser and let the compiler make the decision at compile time (rather than through an if statement at run time):

    /// Anything that is FastPathCompatible
    init<T: FastPathCompatibleValue>(_ value: T) {
        self.init(fast: value)
    }

    /// Anything else
    init<T>(_ value: T) {
        self.init(slow: value)
    }

I think you can get this to work by adding the specific overload as rhx suggests, but then casting to any FastPathCompatibleValue in the unconstrained case, and finally calling the more specific overload using the resulting value (which will be converted from any to some form in the process, also known to compiler folks as “opening the existential value”). Your syntax is a reasonable idea for how to do that more concisely, but for now there’s at least a way to get the effect you want!

2 Likes

Hm. It is neat that I can open the existential value this way, but I have not had success getting it to evaluate at build time.

I suppose I can also shed light on why I'm not using init(fast: some FastPathCompatibleValue) directly: I have some large fixed-width integers (U)Int(128/256/etc) and some algorithms generic over BinaryInteger/FixedWidthInteger. These algorithms have access to methods like init(truncatingIfNeeded:), and are fast if each relevant path is fast. UInt256(truncatingIfNeeded: Int256) is semantically just a bit cast, but the generic implementation iterates over its words, for example.

I could indeed overload the init, but the overload would not be visible in the BinaryInteger/FixedWidthInteger scope, so I would have to duplicate or rework each algorithm to be generic over some IntegerWithFastPaths protocol. A better solution, in my opinion, is adding fast paths at the BinaryInteger/FixedWidthInteger abstraction level, removing the need for oddly constrained algorithms. Some paths I have access to, like as? Self, as? Magnitude, as? (U)Int, and they work fine. Other paths I don't have access to, Int256 -> UInt256 is an example. It is possible, however, to catch this path with a protocol, which is what I had hoped to do, but I need it to evaluate at build time :melting_face:

Related issue (I think): Optimiser fails to devirtualise cast to existential followed by immediate unboxing · Issue #62264 · apple/swift · GitHub

Opening existentials is a very new feature, so there are areas where the optimiser could perhaps do better.

2 Likes