January 31, 2022·5 min read·engineeringswiftinteropownership

Swift gives C++ interoperability a dedicated workgroup

A new workgroup will develop the Swift and C++ interoperability model. The difficult decisions concern ownership, API mapping and the cost of making foreign types feel native.

A C++ function that takes a pointer does not tell Swift enough about what the function expects. The pointer might refer to mutable storage, an optional object or memory that another argument also references. Swift has stronger rules for some of those situations. Translating the declaration into something that looks familiar can therefore change the contract that callers believe they have. That problem sits behind the new effort to give Swift and C++ interoperability a dedicated design group.

Alex Lorenz's January 31 announcement establishes a workgroup for the interoperability model and its compiler implementation. Some C++ APIs, including std::string and std::vector, can already be imported. The announcement describes a group that will refine the design before taking it through Swift Evolution. It does not announce a completed interoperability model or promise that every C++ library now works.

I like Swift's emphasis on making value semantics and absence explicit. Interoperability becomes interesting when those choices meet APIs designed around different assumptions. A convenient imported declaration is useful only if its behaviour remains understandable at that boundary.

The constraints precede the syntax

The existing C++ interoperability manifesto, preserved in the Swift 5.5 release, explains why this work cannot begin by choosing attractive spellings. It assumes that changes must fit Swift's language goals. Breaking Swift's ABI on Apple platforms is outside its acceptable options. Changes to C++ libraries and toolchains also have costs, especially when they require library owners to alter existing code.

Those constraints make automatic import attractive. A library author should not need to maintain an independent API description or an extensive binding layer before Swift can call the library. The manifesto also wants library authors to retain control over the imported API. Automatic translation supplies a starting point, while annotations or overlays can express information that a declaration alone does not contain.

This creates a practical tension. An imported container that keeps its C++ representation can avoid conversions, but Swift code then has to use a foreign collection type. Translating it to a standard Swift collection may make application code easier to write. That conversion can cost time and memory. The manifesto treats these as decisions that depend on the particular type and operation, with some cases needing explicit alternatives.

That is a stronger starting point than assuming all foreign types should disappear behind Swift equivalents. The representation of an argument affects what a call can do without allocating. The ownership of that representation affects which code must keep it alive. Both belong in the design of the imported API.

A mutable reference is not automatically inout

The manifesto's comparison of C++ pointers with Swift inout shows the difficulty in a small example. In ordinary Swift, a function can request mutable access to a caller's value using the following declaration.

func increment(_ value: inout Int) {
    value += 1
}

var count = 0
increment(&count)

The important property is the access rule around the call. The manifesto explains inout as an access to the value that lasts through the function's execution. Other overlapping accesses to that value are prohibited. The storage is initialized, and the parameter does not represent a nullable pointer. Swift can use those guarantees when reasoning about the function.

A C++ pointer parameter does not establish the same requirements. Even a reference supplies only part of the information that Swift needs. Whether arguments may alias, and whether an API expects exclusive access, can depend on a contract that the C++ type does not express.

For a Swift caller entering C++, an inout mapping can impose additional restrictions on the caller. That may prevent an otherwise valid C++ use, but the Swift side can obey the stronger contract. The reverse direction needs separate reasoning. A C++ caller entering a Swift implementation might pass arguments that alias. The Swift implementation could then receive inputs that violate the exclusivity assumptions under which it was compiled.

Bidirectional interoperability therefore needs direction-specific analysis. The fact that an imported function can accept a particular Swift declaration does not establish that a Swift implementation can safely be exported under the corresponding C++ declaration. The manifesto discusses annotations as one way to supply stronger preconditions where the library actually guarantees them.

I find this the most useful test of an interoperability design. The type spelling should communicate the guarantees that remain valid across the call. Matching familiar syntax without preserving those guarantees would make the resulting code harder to reason about.

Const references expose a different tradeoff

The manifesto also examines C++ const reference parameters. C++ APIs often use them to avoid copying an argument. Mapping such a parameter to a Swift unsafe pointer retains a direct representation, but introduces pointer handling into code that would normally pass values.

That choice can produce a surprising cost. The manifesto notes that forming the pointer with Swift's ampersand syntax requires mutable storage. A value that was otherwise immutable might need a mutable copy merely to call an API whose original purpose was to avoid a copy. The apparent fidelity of the pointer mapping can therefore undermine the intended calling pattern.

A more natural value-oriented import needs rules for lifetimes and for what the C++ function does with the reference. The work is about more than whether the argument is marked const. An API can expose assumptions that the importing compiler cannot recover from one qualifier. The group has to consider which mappings are defensible defaults and which need further information from the API owner.

A design forum with a defined next step

The announced workgroup will meet weekly, with technical discussion and meeting notes in a dedicated forum section. Its role complements Swift Evolution. Concentrated design work can happen in the group, while formal community evaluation remains part of the language process. That distinction gives contributors a place to resolve interacting implementation questions without making participation in the group a substitute for public review.

The manifesto already contains enough unresolved choices to justify that arrangement. Its goals include editor operations that follow declarations across language boundaries, as well as low call overhead. An interoperability feature changes the experience of navigating and modifying a mixed codebase, even when its first visible result is simply that a function becomes callable.

For engineers evaluating this effort today, the useful distinction is between the prototype's existing imports and the model the workgroup still has to develop. The announcement establishes responsibility for that development. The pointer and reference examples explain the substance of the job, including contracts that differ by call direction and conversions whose costs belong in the API decision.