[Accepted] A vision for using C++ from Swift

Hello, Swift community.

I'm pleased to announce that the Language Steering Group has accepted a vision document entitled Using C++ from Swift:

This document lays out a vision for the development of the "forward" half of C++ and Swift interoperability: using C++ APIs from Swift. It sets overarching goals that drive the project’s design decisions, outlines some high-level topics related to C++ interoperability, and, finally, investigates a collection of specific API patterns and potential ways for the compiler to import them. This vision is a sketch, rather than a final design for C++ and Swift interoperability. Towards the end, this document suggests a process for evolving C++ interoperability over time, and it lays out the path for finalizing the designs discussed here.

It is a companion to the previously-accepted vision document Using Swift from C++; together these two visions lay out a technical vision for how Swift can interoperate with C++.

This vision document was written by Zoe Carver of the C++ Interoperability Workgroup with the cooperation and input of the rest of the workgroup, as well as some feedback from the Language Steering Group.

As discussed in my general post about vision documents, the Language Steering Group's acceptance of this document is a strong endorsement of the goals laid out in the vision, a general endorsement of the basic approach, but only a weak endorsement of any concrete proposals. All proposals in the vision will have to undergo ordinary evolution review, which may result in rejection or major revision.

Please feel free to discuss this vision in this thread.

John McCall
Language Steering Group

44 Likes

Hey John! This is really exciting!

Another example is a C++ namespace, which can be mapped to an empty enum, a common pattern in Swift.

Could we use this opportunity to introduce namespace as a first-class concept in Swift? The need comes up fairly often, an empty enums always felt like a dissatisfying workaround. It would be great to fix this before C++ interop cements empty enums as the Swift equivalent of a namespace.

15 Likes

If name mangling is kept the same it shouldn't matter. I think it is complicated since there have been pitches for submodules (namespaces on steroids), but there are so many other features in the works.

2 Likes

The C++ Interop feature will be introduced in a way that allows import rules to change over time without breaking current users, so if Swift ever adds a native namespaces feature, we'll be able to adopt it in interop without any trouble.

18 Likes

I've written something up about that here.

4 Likes

The current draft of the document "Using C++ from Swift" often strays into 'language wars' territory. It would be far more effective if it simply discussed the philosophy for adapting C++ to Swift without discussing the pros and cons of each language. It's particularly bad in this case because it makes a number of assertions about C++ that are only partially true and creates "straw man" arguments that any C++ programmer would recognise in an instant. In addition to putting off programmers considering using Swift, it makes it look as if the author has a poor grasp of the language.

Consider the code in the Mutability section for example. Only someone with a poor grasp of C++ would write code like that. If the intention was to pass a value to append to an object passed by reference 'n' times, why not simply pass the string to be appended as a value rather than a reference? And if the appended object was potentially expensive to pass as a value, anything working on two references would check that the input parameters weren't at the same address (and respond accordingly). Moreover, Swift has exactly the same problem - consider what happens if you write the same function with references to 2 instances of the same class rather than a struct. You might say, "but you shouldn't do that!", but that's exactly my point about the C++ example.

And memory management? The fact is, you can come completely unstuck with Swift too (and very easily). It's a matter of understanding what you're doing. C++ simply gives you choice about resource management:

  • It can be all manual
  • It can be reference-counted (exactly like Swift if you choose)
  • It can be a custom allocator (including garbage collection if that floats your boat)
  • It can avoid all dynamic heap allocation
  • … and so on.

Swift makes it easier by removing those choices - for most App developers, that's a very good thing. But C++ provides those choices for programmers who need them, and that's fine too. You're wasting your breath encouraging them to use Swift in those contexts because it isn't appropriate for that job.

Everyone should accept that all programming languages exist for a reason - just pick the language that best suits the context. I've work extensively with many languages (including Swift and C++) and I like them all - but just for different purposes. It's really grating to see deprecations made about C++ in this document, and I feel it would be much improved by just sticking to interoperability without judgement.

7 Likes

It's not the intent of the document to be a salvo in a language war. If there's something we can clean up about the tone, we should.

With that said, though, I don't think the document needs to walk gingerly around acknowledging some of C++'s defects: in particular, memory safety is a real problem in C++, and any interoperability feature between C++ and a language with a stronger basic safety story is going to have to talk about it.

I disagree with your assessment that only someone with a poor grasp of C++ would write code like that example. In my experience (which is not negligible!), the way C++ programmers write code by default is heavy on reference/pointer/iterator/view parameters and is absolutely prone to problems like that. It's straightforward to fix that example by taking the string by value, but few programmers try to do that proactively in every single function they write. And of course taking the string by value introduces performance overhead in the common case in order to avoid a corner-case semantic problem; a more typical response would be to try to handle it by comparing pointers and doing something different, which is a fairly common idiom in user-defined assignment operators (where it's enough of a recognized problem that the precaution is often done proactively).

You're correct that a similar problem arises in Swift with classes, but the fact is that Swift provides (and encourages the use of) language tools like structs that avoid this problem, and C++ does not — it only provides tools for fixing the problem once it's affected you. And that is specifically a problem for interoperation because Swift has to make decisions about what it's going to allow when calling into C++, despite C++ not providing adequate information about whether allowing aliasing is important (as it often is with the APIs around, say, iterators).

10 Likes

I think this is the crux of it. I’m not sure it’s possible to adequately discuss how interoperability works between any two languages without delving into how their differences might violate each other’s conventions and such.

2 Likes

That's all true, and I'm sure nobody who has been investing so much time and energy on C++ interop does so because they want to get in to 'language wars' territory and belittle C++.

But at the same time, the document starts with:

Swift's memory safety is a major feature of its design, and C++'s lack of safety is a major defect. If C++'s unsafety is fully inherited when using C++ APIs from Swift, interoperability will have made Swift a worse language, and it will have undermined one of the reasons to migrate to Swift in the first place.

Which, IMO, does come across as a bit superior. I know plenty of C++ developers who, whilst they acknowledge it has issues, still very much like the language (and I know plenty of Swift developers who feel the same way about Swift), and things like this could alienate them right from the start. It's a bit much very early, and it sets a bit of a combative tone.

Perhaps it is worth including a bit of background about the kinds of unsafe behaviour Swift was designed to prevent. Even just a little bit.

2 Likes

in my opinion this is because C++ is widely used in corporate settings where there is a lot of short-term thinking and little concern for best practices. naturally, the average quality of rust code will be higher than the average quality of C++ code, but this is because of the different incentives under which C++ developers tend to work, compared to rust developers. (swift of course is somewhere in the middle of this spectrum.)

to me it is not hard to understand why a language becoming more popular in commercial settings causes the average quality of code written in that lanaguage to decline.

3 Likes

I don't think this is hyperbole given the scale of problems stemming from the lack of memory safety. Google's study into this space is a good example I think: Memory safety

1 Like

I'm not disagreeing about the importance of memory safety; what I'm saying is that "C++ is broken/unsanitary and we should only touch it with gloves else we'll catch something" is perhaps not the best way to get C++ developers on-side.

It's about the way we communicate the message, not the substance of the message.

2 Likes

i'm not necessarily disagreeing with this, but in my experience the principal hurdle is not cultural acceptance but the following problem:

  1. it is impossible (yes, impossible) to write performant swift without using _modify, __shared, __consuming, etc, etc.

  2. _modify, __shared, etc. are "underscored" features.

  3. corporate/company policy forbids using "underscored" features.

a lot of other pillars of the swift ecosystem (e.g. @_spi) have a similar problem structure.

1 Like

I don't think the part you quoted said any of that, it only states that C++'s "major defect" is the lack of memory safety and I tend to agree. Perhaps the phrasing of "major defect" is too strong, but my point is given data like the sort gleaned from the Google Chrome study is it really? Maybe it could be "major shortcoming" instead, or something like that.

3 Likes

Talking about differences between languages is fine. But the differences are not inherently strengths or weakness - the document would be just as good if those judgement were simply omitted.

Unfortunately many talk about C++ as it was over a decade ago. Memory management (or resource management in general) in C++ can be stronger than Swift unless you choose otherwise. The reason for allowing a choice is simply that C++ programmers have to deal with situations outside the scope of Swift.

I would approach this document by talking about the wide range of choices C++ provides and how/when those differences can be bridged to Swift. There is no point talking about it in terms of strengths or weakness. C++ provides choice for a reason - and Swift can't fill all those roles.

Re the mutability example, passing strings by value in that context is perfectly sound. I assume you realise that a copy will be made in almost all scenarios anyway (including the Swift code). Trying to avoid that copy is (in most scenarios) pointless optimisation that will only obfuscate the code. And passing by reference vs value may be debated in terms of which is right for a specific context, but no no one would argue that you should simply prefer one over the other. The role of a C++ engineer is to pick the best solution for a specific context.

And while use of structs is recommended, everyone - including Apple - note that this isn't always possible or practical. Swift has classes for a reason, just like C++ allows manual memory ,management for a reason. Yes, it makes life more complex, but it's still an essential ingredient (a strength).

Bottom line, what do we lose by talking about differences without saying, "this is better" or "this is worse"? Taking a neutral stance will communicate just the facts, which is really all we need.

I do not think that this is possible. This is a document outlining how to import C++ APIs into Swift. The idea here is to support Swift developers and allow people to write more code in Swift. So we are coming from the point of view that the people writing Swift code actually want to write Swift code and agree that the tradeoffs make sense for their use case. If we were talking about using Swift code in C++, well that might be a different story, with different goals.

Taking this point of view means that Swift (and its tradeoffs) are framed as the ideal or the thing that we are trying to move towards. We necessarily need to juxtapose Swift with C++ when determining if and how to import APIs. So we need to 1) frame Swift as the thing we are trying to move towards and 2) highlight C++'s shortcomings when attempting to accomplish that.

Swift makes it easier by removing those choices - for most App developers, that's a very good thing. But C++ provides those choices for programmers who need them, and that's fine too. You're wasting your breath encouraging them to use Swift in those contexts because it isn't appropriate for that job.

The document is not explicitly encouraging C++ developers to move to Swift, it is laying out a path for C++ developers that want to move to Swift. As I explained above we aren't trying to make Swift a perfect language for C++ developers, we are trying to provide a path for C++ developers to move to a safer language with stronger idioms (if they want to take those tradeoffs).

Consider the code in the Mutability section for example. Only someone with a poor grasp of C++ would write code like that.

The function is a fairly contrived example because it needs to be easy to understand and fit into an already very large document. The point is that C++ exposes lots of mutable references and in large, complex codebases it is hard to keep track of everything. Swift's model here is very different, and we wanted to highlight the fact that Swift developers will start to see the benefits of this model immediately (even when calling C++ APIs).

5 Likes

__shared and __consuming have been formalized into the language as borrowing and consuming.

8 Likes

The Swift developers in general, and folks working on C++ interop in particular, have collectively written at least tens of millions of lines of C++ code. No one is saying "C++ is yucky". We are talking about the very real drawbacks that are clearly apparent to anyone who has used any tool professionally for decades. "Liking the language" is neither here nor there.

C++'s lack of memory safety is a major defect. Full stop. This is not a subjective statement. Using C++ also has benefits, and for some problem domains, those benefits outweigh the defects. Swift also has major defects, which we are constantly working to improve. Every language does. Admitting that tool has defects is the necessary first step in working to address them. Identifying them is not being combative, it's being honest.

Further, any interface between memory-safe and memory-unsafe code is fraught with peril. Memory-safe code is able to assume that certain invariants are always satisfied by virtue of being memory-safe, and interacting with memory-unsafe code allows the potential for those invariants to be violated in ways that compromise the guarantees on which memory-safety depends. I.e. if the interfaces that span the boundary between these two languages are not designed with care and attention to these issues, the result will be that code written in a mixture of Swift and C++ could actually be less safe than code written entirely in C++. All of which is to say, if we can't be honest about the defects in the tools we work with, we're going to shoot our damn feet off, and that's not OK.

17 Likes

This is tricky, because the paragraph you quoted pretty clearly does not say that there's nothing to like about C++; it says that the lack of memory safety in C++ is a major defect that Swift has to treat carefully. I think you're right that both (1) many C++ programmers acknowledge that defect and (2) many C++ programmers will find other people acknowledging it off-putting, but I don't know how to square that circle.

5 Likes

I feel this also misses the point.

I'm not arguing for one second that memory safety is not critical, or that Swift doesn't improve on C++ in some ways with regards to safety. I just think we're communicating that in a very blunt way -- we don't even bother introducing ideas such as memory lifetime management or signed integer overflows before making sweeping statements condemning C++ in favour of Swift. I think it would make the document more approachable if it were based on those kinds of specifics rather than sweeping generalisations.

I mean, to what extent is Swift even safe? IIRC, it's only "safe-by-default". The only thing stopping you from introducing the exact same memory safety failures as C++ is that some type names include the word "Unsafe" - and we have constantly retreated on even that tiny marker over time.

For example, APIs such as String.withUTF8 never include the word "unsafe", and most call-sites will never see that word. Yet it gives you an unsafe pointer, and you can walk over the end of it, deallocate it, whatever you like. How is that any better than C++?

Recently, I mentioned that the custom actors executors proposal introduces a boatload of unsafe behaviours without ever mentioning the word "unsafe". Not a single change was made to the design. You can completely break memory safety if there's a bug in how you implement your actor or executor, leading to the same kinds of UB as in C/C++.

So we have to be honest about that, and starting the conversation by saying things like:

C++'s lack of memory safety is a major defect. Full stop. This is not a subjective statement.

is not helpful, and ignores the reality of what Swift can realistically do to improve on C++'s language model, and which compromises we have had to accept along this language's journey.

3 Likes