Hacker News new | past | comments | ask | show | jobs | submit login
Swift and C++ interoperability workgroup announcement (swift.org)
122 points by pjmlp on Feb 1, 2022 | hide | past | favorite | 61 comments



Hello HN! C++ interop is a project I've been working on for a year and a half now, so you have no idea how cool it is to see this on the front page. Really made my day. Anyway, there are some really great questions in this thread (and some slight mis-understandings), if you want your questions answered, feel free to post them as a reply to the announcement on the Swift forums (linked above). I'd be happy to answer them :)

(To keep things organized, it would be great to have all the Q&A happen in one place, that's why I'm asking you post it in the forums. And remember, every question is valid, so don't be shy!)


Thanks for all the work! When do you think it would be production ready?


I wish them the best, but getting two of the most complicated languages available today with widely different semantics talking to each other is going to be a tall order.

I wish they'd start by standardizing something equivalent to "extern C" in Swift, so other languages can consume (wrapped) Swift libraries without having to implement support for the rather complicated Swift ABI.


> I wish they'd start by standardizing something equivalent to "extern C" in Swift

There is the unofficial (but very relied upon) @_cdecl, if you're OK with needing to update your code if/when that eventually gets standardized. That attribute will take care of the calling convention difference for you.

https://github.com/apple/swift/blob/main/docs/ReferenceGuide...


I'm aware, but it's still unofficial and I've been burned by depending on such things before. Hopefully it'll become official soon.


I think there's space for a high-level ABI that specifies little more than C + VTables + callback register (and optionally ownership information, even if ignored by the caller or host) that multiple languages could decide to use for interop. COM already exists and its a shame that it simultaneously does too much and too little to aid in safely exposing APIs, but I would love to see its ideas pushed further.


I've discussed a similar concern in another hackernews post, though there I suggested improvements to the C language itself since it's the one standard everyone is somewhat forced to follow.

The problem with such an ABI is getting people to use it, there's no single entity with enough power to enforce it.

I've thought of tackling the problem in another direction, making a simple C-like language (but slightly higher level) which uses the C ABI but with proper support for slices/strings/interfaces/errors/etc. and automatically produces wrapper code for mainstream languages (C++, Java, C#, Swift).

The wrappers wouldn't be idiomatic code in the host language, but they'd be directly usable, whereas right now wrapping a "C" library (even when produced by a higher level language) requires recovering all of the semantic information in an additional, library-dependent high-level wrapper.

For example, some SMT solvers expose quite nice C APIs, but to use them in say... Python, you'd need to use ctypes and a lot of low level code, which is why they go out of their way to expose wrapper APIs.

Some solvers like Yices2 have really low level Python wrappers, which mostly take care of the memory management, unpacking lists into a pointer and a length, and string conversions. There's no reason that couldn't be automated with a slightly higher-level than C language.


Focusing on the "you have now N + 1 standards" problem, I think following the Language Server strategy is the way to go: you don't force people to support it, you focus on specifying an API that support a handful of representative bridges (let's say Python <-> Swift <-> Java <-> Haskell), with a builtin method to evolve and then let people realize that supporting this new ABI immediately brings support for every other language that supports it in an idiomatic-ish way.


Yes, the wrapper generators would not be built into the compiler, they're just little pieces of software that generate the relevant code (anyone could develop one in their own language in a couple of days).

The language exists merely as a way of directly interfacing with the ABI, a way to get the libraries out the door so interest in the ABI exists (same as how VSCode made LSP popular).

The problem with doing Python <-> Swift is that you cannot (and should not) try to expose the entirety of Python and Swift to either side. So you need an "extern ABI" solution to expose your Swift code, except this is not built in to the Swift compiler so now you are in trouble.

If you go the external route you end up with SWIG, which is something you only use when you have no other option (CVC4 moved away from it for example).

Having a language that directly supports the ABI sidesteps this issue. All code written in the language works with the ABI directly so you get the wrappers for free without thinking about how to implement them. A lot of libraries are written in C directly for this very reason!

Once it becomes popular enough, languages would implement "extern ABI" directly, and can now talk to each other over it without the middleman. Same as how a compiler with LSP support can be used in editors that are not VSCode.


> The problem with such an ABI is getting people to use it, there's no single entity with enough power to enforce it.

There is on Apple (XPC), Microsoft (COM/UWP) and Google (JNI, Android IPC, Fuchsia IDL), IBM (TIMI / Language Environments).

The problem are UNIX FOSS clones that lack such overlords.


Those OS solutions aren't cross platform (obviously) so they're only used reluctantly for the smallest amount of code necessary to interface with the platform. Microsoft went out of their way to support WinRT in Rust because sure as heck nobody else was going to.

That said, if IPC solutions count (which to me they don't), then the Linux standard would be D-Bus.


For me they count, because it is a way to improve overall application security.

icrosoft went out of their way to support WinRT in Rust, because they want to use Rust themselves, it wasn't out of love.

Same applies with Google's support for Android IPC and Fuchsia IDL for Rust.

Naturally the platforms with headless management like UNIX FOSS clones will never adopt those solutions at scale, hence why nobody cares about D-BUS.


Oh don't get me wrong, for platform APIs IPC is probably the way to go. Microkernels are built entirely around IPC after all.

But I don't want to have to start a separate process and serialize data across a socket to call functions in every library that's packaged with my software, it's too expensive.

Even if the same IPC approach is used in-process and we accept the serialization costs (which is fine), why would library X use this approach and not another one? Specially if it needs to also run on macOS? Different IPC solutions per platform? Nah.

My point about Microsoft is that, had they not supported Rust themselves, it's likely nobody would have bothered with Rust projection for WinRT. That shows that even the OS devs lack the "strength" to enforce these things.

C is the glue everyone uses and will continue to be so for the foreseeable future.


> My point about Microsoft is that, had they not supported Rust themselves, it's likely nobody would have bothered with Rust projection for WinRT. That shows that even the OS devs lack the "strength" to enforce these things.

Nope, it shows that Rust would be irrelevant for most Windows shops, and Microsoft wants to use Rust instead of C++ in certain workloads like they are already shipping on Azure IoT.

Same thing with Android and Fuchsia, good luck using your C glue there.


Most Windows shops could not care less about WinRT. Nobody is going to develop libraries that expose a WinRT interface besides Microsoft, nobody is going to use it unless they must, just as almost nobody exposes COM APIs unless they need to run within Office or something. WinRT is so unpopular that all language support was developed by Microsoft themselves.

I think we're talking over each other. I'm not following your point. Do you believe WinRT is going to become the new standard for cross-language interoperability, even on Windows?

Libraries are either OS libraries, in which case something wraps the "platform ABI" and hides the interface (see SDL on Android for example), language specific (.NET libs are only for .NET languages), have custom tailored language A <-> language B binding generators (e.g. Shiboken to expose C++ Qt to Python), or expose a C API.

Even tools like Xamarin to expose Android APIs to .NET rely heavily on intermediate C glue because that's the only alternative (besides IPC, which is not desirable) to avoid running on the JVM.


You just don't get the point, WinRT and COM are exactly the same thing.

COM with .NET metadata files + IInspectable in addition to IUnknown => WinRT

Since Windows Vista all major APIs are COM, you cannot avoid them unless you stuck in pre-historic Windows XP.

SDL on Android works, because Google has exposed the required APIs for game development to NDK, everything else requires a JNI call, or having Java code being called via Android IPC (binder).

Xamarin performance is quite bad versus Java/Kotlin, because they have to cross two layers to do anything meaningfull,

.NET => NDK => JNI and then for every Android event that gets triggered Java/Kotlin => JNI => .NET

Xamarin runs on ART (there is no JVM on Android) anyway, because the only kind of binaries that NDK allows for in proper Play Store applications are shared libraries loaded by Zygote process that owns ART.


All of what you just said is true and yet at the same time utterly pointless to this conversation :| so no I do not get the point.

Yes to talk to the platform you need to use the platform ABI, whatever that may be. OS devs obviously can force people use these ABIs to talk to the platform, otherwise it is literally impossible to talk to the platform. That's true on Linux as well btw, if I want native Desktop notifications across Desktop Environments I need to use the freedesktop standard (based on D-Bus).

I can ignore that, same way I can create a pseudo "notification" with the old Windows API instead of the proper WinRT APIs (heck, even Microsoft Teams does), but that's irrelevant because it's not what we're talking about.

This discussion started from a post on Swift <-> C++ interoperability. Swift has a stable ABI. C++ could directly implement that ABI, but Apple does not have the power to enforce that.

So what point are you trying to get across to me?


Teams is Electron junk using Web APIs for PWAs, again not C.

Apple has the power to enforce whatever they feel like doing on their own platform, that is why watchOS has a forked LLVM bitcode as executable format, regardless of how unstable the LLVM one is, C ABI matter zero there.

Just like they have a Safe C compiler for iBoot firmware, or have extended C language for lambdas (blocks).

Clang on Apple is different enough from upstream. that it warrants their own column on cppreference.

We are done here, there is no point getting across anyway.


I have no clue what you're on about and why you insist on telling me things I already know that are unrelated to the topic.

Indeed this conversation is not helpful for either of us, I still don't know what you're trying to discuss. If you're under the impression that I claimed OS providers have no power over their own platform, I never once claimed that.

I claimed they cannot enforce their crap as a standard outside of their platform (sometimes not even on the platform, UWP died for all intents and purposes), which is demonstrably true.

Were you under the impression that I was not aware Microsoft Teams is an Electron app? And how its old non-native notifications work and how Electron wraps OS APIs that aren't web standards exposed in Chromium?

Honestly you've not only repeatedly insulted my intelligence in this thread, but you've also ignored all of my attempts at having an on-topic conversation. Normally that would make me label you as a troll, but you've had many valuable contributions across this website so I'll just assume you had a shitty day and leave it at that.


This is really nice to see; apart from the weirdly named, macOS specific NS* apis, Swift has been a real pleasure to use and, refreshingly, feels like a language designed for humans first. I hope this means Swift has a chance to eat Rusts’ lunch :)

Does anyone know how c++ classes and struct will interact with ARC?


> The biggest semantic impedance mismatch between C++ classes and Swift classes is that Swift classes are reference-counted. It is certainly possible to import a C++ class as a Swift class if it is intrusively reference counted, or wrapped in a shared_ptr.

> But what about non-movable C++ classes that are not reference counted? This issue could be worked around by making retain and release operations on C++ classes be a no-op, and introducing an explicit "delete" operation, effectively making C++ class references in Swift equivalent to UnsafeMutablePointer.

There's some information about it in https://github.com/apple/swift/blob/main/docs/CppInteroperab... this is quite long though so there's likely a lot more information. This quote is apparently just one strategy they're considering? At least that's my take on reading it.


NS stands for NextStep.


Surprised to read that the compiler already supports some C++ types? That’s news to me. It’s not clear to me if that’s already in the shipping compiler or in some other version or just a proposal.


Yes, it has been in the Swift compiler for some time now

The original flag was "-experimental-cxx-interop", now it is "-enable-cxx-interop"

See here for examples of it in use, including interop with the C++ std library types:

https://github.com/plotfi/cxx-interop-test


Right, a C interface without obj-c would be enough to make it much easier


I already use C interfaces on C++ code to interact directly with Swift without objectiveC. What I’m curious about is if I can actually stop doing even that for many common types? It appears the compiler supposedly already supports that.


I didn’t know even that was possible and thought that I had to use objc as it used to be. Nice


Objective-C is a superset of C. If there is Objective-C interop, there is by necessity already C interop.


That is not completely true. There could be object round trip support between objective c and swift objects which would not be available through a C interface. Consider ObjectiveC++ as an example.


You are talking about something different, I think. Of course, if you need Objective-C features, you don’t get those from C. But if you simply need C features, you don’t need Objective-C just for that. In the context of a C api exposed over C++, objective C is not necessary.


You can also use this to wedge the C preprocessor into Swift

for example, I can inject variables into the C preprocessor in my build settings and have a C header that defines constant strings with those variables, which I can then import directly into Swift as a module


How do you create the C interfaces from C++ code?


I’m not sure exactly what you’re asking, but it’s just basically writing a C header file as an API, but the implementation is in a source file that is in C++.


I see, so you're just writing the bridge code yourself. OK, I do the same in Objective-C++, so at least it gives me an object-based interface, as well. But saving this step and having a direct Swift <-> C++ would be helpful.


Yeah for me it's just about keeping Obj-C out of the project. It's enough to have two languages, I don't want to unnecessarily add a third.


Don't you already have 3 languages to think about? Swift, C and C++?


The C API is usually syntactically valid C++, so I don’t think of it that way. It’s just an API. Implementation is still in just two languages.


I mean, you also have to be careful to return pointers and not smart pointers and so on. It can be a bunch of work if you need to write a bunch of freestanding functions to wrap the c++ object methods.


Well, you won't return smart pointers if your header file is in fact a C API, since smart pointers are C++ only.

It's usually as simple as calling data() on a vector or std::string (as well as having a function to return the length or size of the data buffer). And if you wrap your header file in the normal C externs, it shouldn't compile if you do things that are not allowed in C, if I remember.

But you are right it can be a lot of work if you have a large surface area. I try to stick to tight, focused APIs, and data is normal C structs which work fine in C++ as well.


Slightly side question -- does this bi-directional interoperability improve Swift adoption to multiple platforms. Swift when it was released had much fanfare and definitely looked like a balanced language but as time passed has been limited to Apple eco-system.


Swift has always directly interacted with C, so I doubt limited support for C++ would make a huge difference in that kind of adoption.


Plus C++ devs often expose a C interface because most languages support that.


I've been spending the last couple weekends porting Thorsten Ball's Monkey language (from the texts Writing an Interpreter in Go and Writing a Compiler in Go) to Swift. A year ago I spent a few weeks playing with Swift on the server but never really made anything happen with it, so this has been my real nuts and bolts introduction to the language.

I found it to be pretty neat, but it's very difficult to determine whats allowed to use outside the Apple ecosystem. The documentation is sporadic and there is only a reference manual. It makes me long for the Rust Book, with its tutorial style introductions. There are of course plenty of Swift tutorials out there, but they expect 1) you want to write an iOS App, and 2) you've never programmed a day in your life before.

The "standard library ecosystem" is very confusing. There's the actual language standard library, which is much like "core" in Rust. Then there's Apple's "Foundation" library, which is the more "batteries included" type standard library most people look for in a language. But "Foundation" isn't... straightfoward to understand. First of all, there's two completely separate implementations. On Apple platforms, you get the real Foundation, implemented in Objective-C with no source code available. On Linux, you get "swift-corelibs-foundation", which is fully open source and written entirely in Swift (really nice to look up what it is actually doing).

But swift-corelibs-foundation has a lot of missing features. To figure out if you're allowed to use something, you better be ready to look up the source code and see if its actually implemented on the Linux version. Or study a very long complicated markdown table in the github repo where they track what they've ported.

Want to develop on your MacBook but deploy to Linux? Better set up a complicated Docker environment so that you can actually see how your code will run with swift-corelibs-foundation. You _can't_ import or build against it on Apple platforms.

A lot of people learn a programming language by spending a lot of time reading the standard library. Well, it's nice that swift-corelibs-foundation is open source and publicly accessible and written in Swift. Unfortunately, it's not a great place to learn swift. Since it's a reimplementation of Foundation, which is itself a port of the Objective-C "Foundation Kit" library, its full of non-swifty concepts and ideas. There are, MOSTLY, more Swifty method implementations hanging off of the Foundation types, but there are also countless bits of API that take unsafe pointers or are fully deprecated and you must learn to ignore (looking at you, NSZone).

Learning Foundation is no easy task either. You can read the swift-corelibs-foundation source, of course. But since it's Swift, types are frequently re-opened and extended in other files so there's no one place to view the entirety of a type. There are the Apple developer docs, but theres no location where they list all the types in Foundation, or all the methods/properties on a type. The docs are handwritten, not generated from comments in the code. Apple recently released "DocC" to generate docs from swift libraries, so I'm hoping this one changes soon. There's the venerable swiftdoc.org which has generated documentation, but it only covers that "core" standard library. Also, certain symbols constantly 404 on it.

Thankfully, implementing a simple bytecode compiler doesn't require much beyond the standard library types. I still occasionally have to import Foundation, but its mostly just to get a single method here or there on a stdlib type, not to use some complicated NSObject based "class cluster".

I'm hopeful that good interop frameworks drive more folks to use the language outside Apple platforms, and real investment gets put into these issues. Don't get me started on XCTest on linux...


You would be better off with a non Apple language, Swift without the Mac ecosystem isn't really worthwhile, just like Objective-C never was (GNUStep is a shadow of what actually means to use NeXTSTEP or macOS).

To give another example, even .NET with Microsoft going full steam with cross platform support, has lots of stuff that will never leave Windows.


This will be useful for Swift for backend applications as this can open up use of C++ libs (in case native is lacking).

Anyone using Swift for high load production workloads yet?


Great, now I want the same for Rust <-> C++


Not a working group, but a library does exist: https://cxx.rs/


The difference is that cxx is doing codegen to create a C ABI compatible bridge. So the same thing most people already do when they want to interact w/ C++ over FFI.

I believe that TFA is actually saying that the Swift compiler speaks C++ directly without doing any codegen and just handles everything at the compilation level. Same end result but a lot of subtle differences. Not having to do codegen is a big win, especially in Rust where compile times aren't terrific and binary sizes can already bloat up due to the statically linked nature.


Yes, cxx.rs is really good for what it is! There's also autocxx that aims at generating cxx bridges directly. What Rust lacks is, to my knowledge, a C++ interoperability working group. cxx.rs (which is useable but by no means finished) could be a topic of such a working group. Direct compiler support of C++ could be another.

A better C++ rust interop story is a critical piece for Rust's success IMO


I hope one does not need to write the signatures manually and tweak the cxx generator, for detecting flags, types and the like just to get it properly working. If that's the case, then that would be tons of maintenance and more trouble than its worth for a more complex C++ codebase.

Best to make that a first party feature built into the language compiler and have it do the majority of the work automatically rather than manually define the C++ types, functions, etc using a third party library.


I've asked around a bit, and so far I've found no language or tool that can directly access C++ libraries without manually writing bindings. I hope that can change sometime in the near future.


I know of two languages:

1. D (https://dlang.org/spec/cpp_interface.html)

2. Nim. Though this is a bit cheap because Nim is a frontend to C(++), so you're really writing C++ that calls other C++ when it boils down to it. See https://nim-lang.org/docs/manual.html#implementation-specifi...


There is one -> Objective-C++ - interop is very seamless (just change file extension from .m to .mm). I do hope as well that will change and we will see some other languages speaking to c++ so easily.


Funny, my cynical brain read the Swift/C++ announcement as two dead languages fighting for relevance, in the face of Rust, by banding together. Fuck Rust <-> C++. Let C++ die! ;-) (only half joking)


So when is Apple going to rewrite IO and Driver Kit, and re-base Metal Shading Language in Rust?

If C++ dies Rust will have a hard time replacing its GCV/LLVM backends.

Cranelift as it stands today won't do it.


>> Let C++ die!

> So when is Apple going to rewrite IO and Driver Kit, and re-base Metal Shading Language in Rust?

Death of a language isn't a clear cut thing that really happens, so everyone has their own definition. Some consider a language dead when they fall from former glory, so for a popular language they don't need to disappear, they can even have multiple thriving niches and still be considered "dead" because they are a portion of the usage they used to have (see Python 2, still widely in use, but I can make argument to call it "dead"). Others consider a language dead when direct competitors are used instead of them for new projects (see Perl). Yet others consider a language dead when there's an active and thriving effort to migrate existing applications to other languages. All of these are varying interpretations of "dead language". I would call Delphi, Perl, Python 2 "dead", yet there are plenty of projects in use out there written in those languages, and in some cases even thriving communities. But even if their absolute numbers don't decrease, the rest of the world might continue to out grow them into "irrelevance" (whatever one might want to consider that to be).

All that to say: C++ doesn't need to disappear to "die", it doesn't need to die for other langs to thrive, and it doesn't need to "die" even if other languages thrive. It is not a zero-sum game.

> If C++ dies Rust will have a hard time replacing its GCV/LLVM backends.

> Cranelift as it stands today won't do it.

You're comparing a 20 year old project (LLVM) with a 4 year old project (cranelift) that have different goals. It's like comparing Rust and Python, or Fiat and Cannondale: they have some similarities, but any comparison is gonna be stretched beyond use.


Well, most Rust vs C++ comments, like the one I was replying to, tend to lack a perspective of what the market actually uses, and how Rust is dependent on C++ toolchains (at least for now), so.


LLVM in Rust seems actually plausible, there are some Rust ideas that probably feel very natural indeed to LLVM core people, in particular as I understand it the niche trick, where Rust knows the valid bits for type A and type B don't overlap, so, it can fit them both in whichever was the largest (e.g. Option<NonZeroUsize>), is something LLVM does a lot in its own internals.


The problem isn't the language, rather the volume of contributions.

Last year, LLVM has reached Linux kernel level of contributions from researchers and compiler vendors.

LLVM in Rust won't have that, and no one is going to rewrite such a massive compiler building infrastructure.

Then we have all the companies that building tooling on top of it, and everything related to the development process that relies on how LLVM is implemented.


Or you know, just use GraalVM


I did a quick check and graalvm doesn’t appear to support swift or c++, so I’m not sure why you’re suggesting it?




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: