Base classes: an intermediate solution

For C++ interop, we want users to have an absolutely amazing expirence no matter the API they’re importing. In order to achieve this, we’re going to have to do a lot of design work to import base classes correctly. This means native casting, calling base class methods, handling virtual and overridden members, and so much more.

I think this will be one of those common cases where the design work is actually the most difficult part. C++ interop in general has a problem: we don’t know, specifically, how it’s going to be used. And this makes the design work even more difficult. But, luckily, we are constantly able to import more and more APIs, and use them (even if it might not be pretty). Soon we will hopefully add a few source compatibility projects, and maybe even start migrating libSwift to use C++ interop. These projects will allow us to iterate on our designs. They will show us what users want and make the best models and designs clear.

Given this, I propose that we add “support” for base classes in two phases. First, we add support for the most basic functionality: the ability to call base class methods, use base class type aliases and sub-types, and read from/write to base class stored properties. This will give users the ability to use most of the functionality they rely on and that will inform us of how best we can properly support base classes in the future. It will show us the most common use cases, and the biggest problems users have. With this information we can re-design how we import base classes and make the experience truly fantastic.


I’ve already started prototyping an implementation of this proposal and I hope it won’t take more than a few days to get a full patch up on GitHub. With lazy member loading, the basic functionality is actually quite simple. Given an API

struct Base { int fn() const; };
struct Derived : Base {};

Today (with lazy member loading) we import Derived using these steps:

  1. Create a Swift struct named Derived .
  2. Issue a member lookup request for all the named members in Derived .
  3. There are no members so we get struct Derived {} .

I propose we modify this to do the following:

  1. Create a Swift struct named Derived .
  2. Issue a member lookup request for all the named members in Derived .
  3. Issue a member lookup request for all the named members in all of Derived ’s base classes.
  4. For each method found in a base class:
    a. "Clone" it into the derived class.
    b. Synthesize a body that casts self to the base class type, then calls that method on the casted self.
  5. So we get struct Derived { func fn() -> CInt { static_cast<Base>(self).fn() } } .

You can imagine something similar for VarDecl s: we can just synthesize accessors that cast self and lookup the field on the casted self base class.

For now, I think we shouldn’t touch casting, virtual base classes, etc. This basic behavior will allow us to start using even more APIs and will therefore allow us to get lots of information about not only how base classes are going to be used, but many C++ libraries. This will help us iterate and improve how we import C++ APIs in general.

Here’s a related post discussion how we might import base classes more generally.

12 Likes

Wonderful to see progress on this! I don't interact with C++ too often, but given that I'm currently working with a library which builds without RTTI (MLIR) I'm happy that this intermediate solution doesn't seem to require us needing RTTI.

1 Like

Amazing, Zoe! Cheering you all on from the sidelines!

Swift's C++ interop is one of the most exciting things (slowly) happening in the world of tech for me. Love to check back in on the forums under the cxx-interop tag every month or so and see what's new.

As soon as it's in a semi-usable state I'd love to give it a run and offer feedback if its helpful at all. I'm on Windows and have been looking at building Swift libs/binaries that interop with existing C and C++ libraries.

I saw the changelog for Swift 5.5 and the work done around Actors and concurrency guarantees I think makes a really interesting case for trying Swift in places like audio plugins, which are realtime and heavily concurrent

(Not sure how well Swift in general is suited to this, it's typically done in C/C++, but I'm not sure you couldn't if you controlled allocations)

6 Likes

Thanks for the encouraging message, Gavin. That's really great to hear. I'm glad others are excited about this as well :slight_smile:

Having the community provide feedback on adoption would be extremely valuable. I think we are just about to approach the time when this will start to be possible, as C++ interop begins to mature more. When the time comes, I'll let you know, and probably post something here on the forums.

7 Likes