[Pitch] Custom Type Casting Support

Hello, this is a pitch for allowing of custom dynamic casting support. I'll first introduce the feature and the motivation for this pitch.

Introduction

The idea of this feature is to allow for classes to hook into dynamic cast failures and potentially provide the casting implementation themselves. This feature is for low level libraries, mostly for those interoping with other languages, where defining the full type enclosure in native Swift code is either impossible to do, or has performance issues. There are two main scenarios where this will be useful, which I'll highlight below. The APIs in this example are created by a code generator and are wrappers around a low level library which bridge between Swift and some other language.


class A  {}
class B: A {}

protocol Scenarios {
    func someTypeErasedValue() -> Any
    func someBaseType() -> A
}

Let's say for both methods above, the type that is returned from the Scenarios API is of type class B. We don't know at code-gen time what these objects are, so the type casts (i.e. let b = someTypeErasedValue() as? B or let b = someBaseType() as? B) will fail.

The feature

protocol _DynamicCastFallback: AnyObject {
    func castTo(_ type: (any Any.Type)) -> Any?
}

Motivation

We have a code generator (GitHub - thebrowsercompany/swift-winrt: Swift Language Projection for WinRT) which is used to generate code to interop with the Windows Runtime. The Windows runtime is built on top of COM, where type casting is done through IUnknown.QueryInterface. There are currently a few problems that we have trouble solving:

  1. There is no guarantee we know the full enclosure of interfaces at compile time, resulting in us being unable to generate fully correct code
  2. In order to support the above scenario, we have to do expensive type introspection to return the appropriate object, potentially for no real reason if the user doesn't actually do an upcast.
  3. There are scenarios we simply can't support today (i.e. generic WinRT types)

Note: While COM is the main use case and motivation for this pitch, it is not tied to COM in any way

What this pitch is not

This pitch is not is for a way to escape or re-define Swift's casting abilities today. We simply provide a last chance effort to perform the cast when it would otherwise fail.

Prior Art

.NET has this functionality through the IDynamicInterfaceCastable interface. The same rules can apply here that only reference types (classes) can participate in this behavior.

5 Likes

Is there any reason this has to hang off the built-in as keyword, instead of calling your castTo method directly?

1 Like

I suppose one benefit is that it would allow users to hook on to the dynamic casting of arrays, dictionaries, and sets. That would especially be helpful for force casting, which can be optimized to be lazy. Then again, array.lazy.map would work for that purpose as well, with the downside of not returning an array type.

@Slava_Pestov we can always do that, but there are drawbacks i can think of, and i'm sure there are more I haven't thought of:

  1. it's unnatural for swift developers to do that. they will use as, bang there heads why it doesn't work. go look up documentation. forget about it the next time they run into it, etc. if you're working in a cross-platform code-base there shouldn't be different ways developers have to think about doing simple things.
  2. you lose compiler checking that the cast is valid.

this wouldn't affect any current behavior. once a type cast has fully failed, the runtime then queries the object for this protocol and calls the method on it.

Note: we probably want an is method as well.

1 Like