Swift and C++ interoperability workgroup announcement

Over the past few years there has been a huge amount of interest in bidirectional interoperability between Swift and C++. Thanks to the hard work of zoecarver, egor.zhdan, gribozavr, Michael Forster, Marcel Hlopko, scentini, and many other contributors, the Swift compiler is now able to import and use some C++ APIs, including C++ standard library types like std::string and std::vector . Additionally, a C++ interoperability manifesto that describes the goals and the design for the implementation was written and published on github.

To advance the interoperability support between Swift and C++, we are announcing the formation of the Swift and C++ interoperability workgroup as part of the Swift project. This workgroup provides the framework for those interested in Swift and C++ interoperability to work together to refine the interoperability layer's goals and design. Additionally, the workgroup will establish an ongoing discussion about the related Swift compiler changes to implement interoperability support.

Workgroup Details

  • The workgroup is responsible for developing and designing the interoperability model between C++ and Swift.
  • The workgroup will meet over a video call once a week.
  • The workgroup will initially focus its discussion on how to rapidly iterate the development of the interoperability model between the two languages.
  • The workgroup complements the existing Swift evolution process. The workgroup will design the interoperability model first, and then will bring it back for formal evaluation and discussion with the Swift community via the Swift evolution process.

Swift Forums

A new "C++ interoperability" sub-section of the "Development" section on the forums, open to everyone , will host the relevant technical discussion posts on C++ interoperability. Alongside technical discussion, it will include meeting notes from the workgroup meetings.

Get Involved!

We would love to hear from anyone interested in joining this workgroup. Those who participate will need to feel comfortable generally meeting the following obligations in terms of commitment:

  • Dedicate 50 minutes per week that you can use for the interoperability discussions.
  • Actively participate in the work related to the Swift and C++ interoperability project. This can include things like:
    • Making code changes in the interoperability layer in the Swift compiler.
    • Writing user-facing documentation for this feature.
    • Reviewing related PRs and/or proposals.
    • Providing design input for mapping specific code patterns between the two languages.

Please let us know if you're interested in this effort and if you'd like to join the workgroup by responding to this forum post below. We are planning on forming the workgroup in the next week or two once we have received the responses from this community. The initial set of participants will include the following community members:

Alex Lorenz (@Alex_L) will be responsible for setting up the meetings, going through the discussion points, and posting the agenda and the meetings notes on the Swift forums.

Thanks,
Alex

132 Likes

Awesome job to everyone involved! The importance and effort of this undertaking can't be overstated.

5 Likes

good news

1 Like

:muscle: awesome!

2 Likes

It's awesome to see this getting so much interest! Thanks for the great post, Alex.

I've been seeing a lot of questions about C++ interop in response to this post, and I just wanted to use this as an opportunity to answer a couple common ones. That being said, feel free to post any questions here, and I'll do my best to answer them.

Is C++ interop supported/shipping?
No. C++ interop is not supported (yet). Some things are working (quite well actually), so feel free to try it out, if you're interested. But I want to be very clear: C++ interop is not a supported feature.

An experimental version of C++ interop has shipped with most of the recent Swift compilers, however, it is still very unstable, and you will likely see crashes and mis-compiles. In the past few weeks I've been making stability a priority, so future compilers will hopefully be more stable.

If you want to try it out, play around with it, test it, etc. you can use -Xfrontend -enable-cxx-interop. But remember, this is not a supported feature, and it is likely to cause crashes and mis compiles.

How does this fit with Objective-C/C interop?
C++ interop composes nicely with existing Objective-C and C interop. On macOS Objective-C interop is enabled by default (and required) so you're really going to already be using Objective-C++ interop (allowing use of both Objective-C and C++ APIs).

On other platforms, C++ interop is pure C++ interop; you can't use Objective-C APIs (at least by default).

Does this require any work from the user's side?
Not really. By default, you should be able to automatically consume all (or many) of your C++ APIs without any extra work. They will just be automatically imported like Objective-C APIs. That means you don't have to define any bridging functions or API mappings. The two exceptions to this are: 1) you will still have to create either a module map or a bridging header so the compiler knows what headers to import and 2) ideally you will annotate your APIs with _Nullable and _Nonnull attributes (but this is optional).

Does interop jump through wrapper functions?
No! One of the goals of interop is to provide a fast, native-feeling experience. So, unlike bridging interfaces in most other languages, Swift's C++ interop will import types natively and call functions directly. This means you're getting the absolute maximum performance possible (note: there are a few cases where the compiler has to create a thunk or something, but these are rare, and usually inlined away).

Not only is there little to no performance overhead, but in many cases, it's actually faster than going through a C bridging layer. Let's take a common example: bridging std::string. If a user relies on a C++ API that uses std::string, they have to copy the contents of that string into some heap memory and pass that heap memory to Swift through a C bridging layer. Because this is manually allocated heap memory, the Swift program has to remember to deallocate it. So, not only does this introduce an expensive copy, but also introduces unsafety and complexity. C++ interop allows users to get rid of this kind of bridging code. Instead, they can call their C++ API directly, so they don't need to copy their string onto the heap. When the object is done being used, the compiler will invoke string's destructor. Now, the program is safer, faster, and simpler!

30 Likes

I would love to be a part of this workgroup to learn and/or contribute as much as I can.

2 Likes

I’ve been watching your commits recently, and I’m happy to see this moving forward.

Being able to use C++ libraries in Swift will be a massive boon for the community, especially if it becomes possible to put C++ source files directly in a Swift package and use it like anything else. C++ doesn’t really have a dominant package manager, so that could be a very persuasive way to encourage Swift’s adoption.

6 Likes

This is great news. C++ interop is a huge benefit for some of us. I don't do this now but I remember I had to wrap C++ code into Objective C wrapper classes which was quite messy.

Please clarify no this. I can now use Obj-C in otherwise swift app on iOS, will I lose this ability? Or do you mean, depending upon some compiler switch I'd be able to use either Obj-C interop or C++ interop but not both? Is this going to be addresses in the future?

How std::string interop will work in practice? For example I have SwiftUI or UIKit app that wants Swift.String, and it can tolerate NSString (as NSString is bridged to Swift.String reasonably well). And I'll do the model layer in C++ with std::string, will I need to recreate String from std::string to show it in UI or not?

(I remember there was a dodgy CFStringCreateWithCStringNoCopy -> NSString -> String route for those who are brave but that's a last resort and when I tested it it only had the "no copy" behaviour for certain encodings).

1 Like

What I'm saying is that today, when you develop on macOS, you can chose to either use Objective-C interop or Objective-C++ interop, and those will likely continue to be the only two choices (at least in the short term).

By other platforms, I really mean Windows and Linux.

Right now, you can either chose to use std::string directly, or convert it to a Swift String by doing something like this: String(cString: std-string.c_str()) where std-string is an instance of std::string. Depending on your use case, it might make sense to do one or the other (for example, if you only use it with C++ APIs, then it probably doesn't make sense to ever convert it to a Swift string).

I think some implicit bridging between these two types might be nice, but that's a bit further down the road :)

3 Likes

Hi, I'm interested in being included into this workgroup. I've been working a lot with Swift Package Manager to integrate and install C++ packages (both Apple & cross-platform ones), have few insights regarding this and can contribute to this effort.
I have to often use C++ packages from Swift, so this is something I can share my findings on.

5 Likes

I would love to help where I can! I am still a student, but have experience with interoperability between Python and PowerShell. A little bit of experience with C++.

1 Like

It's really exciting to see so many responses and so much interest in contributing to the interoperability project! I am currently reaching out to everyone who expressed their interest in joining the workgroup and/or contributing to this project. You can continue posting here if you wish to join and/or contribute, and you can also message me directly as well.

3 Likes

This is a little confusing, Zoe. I understand how it's an example of the program being safer and simpler, but the overwhelming cost of bridging to that API is the memory allocation for the std::string's storage, which AFAICT remains an issue unless you have modified std::string to be able to adopt Swift.String storage. Has that been done?

Once std::string is bridged over to the Swift program, the cost of converting a std::string to a Swift.String still exists, that's not what I'm talking about.

What I'm saying is that often users have to consume an API that, for example, returns a std::string. Without C++ interop, the only way to get that std::string to Swift is through a raw pointer (or something similar). And to safely convert a std::string to a raw pointer, you have to manually allocate some memory on the heap, copy the contents of the string into it and manually manage that memory's lifetime. That's the slow part that I'm talking about (the extra, unnecessary allocation + copy).

3 Likes

I might be dreaming but I believe that would be really cool if std::string / std::array / std::map, etc were somehow actually using their Swift counterparts, on a flip of some compiler switch (perhaps with some limitations, as it is an explicit opt-in)... That would be make it tax free bridged.

What about the other way (C++ -> Swift)? There you might want to bridge Swift data structures to underlying C++ structures.

You could do the bridging you are thinking about by rebuilding std:: containers libc++ support routines using the Swift primitives. You would then use the new libc++ instead of the system version.

That would seem to be a lot work to make sure the new libc++ API surface behaves the same as the system version. And, with C++20 currently being integrated to the system libraries, and C++23 now in development, some entity would have to keep on top of that development to make sure the libraries mesh

I think it’s impossible, sadly: they aren’t even functionally equivalent. C++ strings don’t work at the level of Unicode: the equivalent in Swift is pretty much NIOCore.ByteBuffer or Foundation.Data.

1 Like

Me neither; std::string adopting Swift.String storage is about conversion in the other direction. IME this interop stuff can be difficult to talk about because as in this case, nobody is ever specific enough :wink: (including me).

It would probably help to invent some terminological conventions to eliminate these ambiguities.

1 Like

I think it might be possible to make a "shared" Swift String from a std::u8string, but I agree that there's no way to automatically bridge a regular std::string.

It's been a very long time since I wrote any C++, but I find it unclear what it even means to have std::string adopt String's storage. IIRC, C++ strings make deep copies when they're passed around anyway (no reference counting ofc), and the C++ standard forbids copy-on-write, which is why people prefer to move them. Also they're mutable.

But you can't, in general, move a String's storage in to a std::string unless Swift no longer needs it (either via an explicit move or optimisation) and it turns out to be uniquely-referenced; although String may be able to adopt std::string's storage using our support for shared strings.

I could see something like string_view working, though.

1 Like