Report: Swift and C++ interoperability project progress in the Swift-5.7 time frame

This post presents the progress that the Swift and C++ interoperability workgroup has made during the first eight months of this year by highlighting the major documentation and compiler changes that landed in that time frame. The span of this time frame stretches from the initial workgroup announcement until the middle of September of this year when Swift-5.7 was released. Note that the majority of compiler changes in this post haven’t made it into the Swift-5.7 release, but they will make it into the next planned release of Swift.

:busts_in_silhouette: Workgroup

The Swift and C++ interoperability workgroup now has a page on the Swift website that talks about what the workgroup does and describes how to join it.

:memo: Documentation

:rocket: Using C++ APIs From Swift

  • Designed and implemented a safe model for importing C++ types and their members that prevents accidental misuse of inner pointers and references in Swift. The vision document provides more details about this model.
  • Extended previously prototyped support for bridging C++ types with reference semantics to Swift class types by adding support for bridging custom C++ reference counting operations to Swift.
  • Added a C++ standard library overlay to allow easier conversion between Swift and C++ standard library types.
    • For example, you can now convert a C++ std::string to a Swift String using the String(cxxString:)initializer.
  • Designed and implemented a safe and ergonomic iterator bridging model from C++ to Swift.
    • This allows the use of C++ collection types like std::vector in for-in loops in Swift, and also provides access to Swift’s collection APIs like map and filter for such types.
  • Improved support for bridging C++ operators to Swift.
  • Improved Swift’s compatibility with the Foundation framework in Objective-C++ mode (i.e. when C++ interoperability is enabled).

:boomerang: Using Swift APIs from C++

  • Designed and implemented support for bridging Swift structs, enums, and classes to C++.
  • Added support for bridging functions, methods, properties and initializers to C++.
  • Designed and implemented initial support for bridging generic functions and types to C++.
    • This allows C++ code to call Swift generic APIs that don’t have generic requirements.
  • Added initial support for using Array and Optional Swift standard library types from C++.
  • Added an overlay for the Array type that allows it to be used in C++ ranged-based for loops.
  • Added initial support for bridging functions that throw to C++.

:railway_track: Looking ahead

In the time frame of the next Swift release, the workgroup is going to focus on taking some of the currently prototyped interoperability features through Swift’s evolution process. The workgroup intends to finalize and publish the vision documents and is planning to submit several evolution pitches. The workgroup is going to prioritize bug fixes and stabilization of these compiler features as well. Additionally, the workgroup is planning to work on related ecosystem features like improved debugging and editor tooling support for projects that use interoperability.

I would like to thank the members of the C++ interoperability workgroup and the Swift community for their contributions to this project. The workgroup has made great strides towards the vision of greater bi-directional interoperability between C++ and Swift and we’re excited for what will come next.

53 Likes

Thanks for writing this up and keeping us informed. Much appreciated!

2 Likes

Is there a reason why this is not String(cppString:)? “cpp” is generally understood to refer to C++: it’s the standard file extension for C++ implementation files, and GitHub-Flavored Markdown recognizes it as the language name for C++ when introducing a code block with syntax highlighting. I don’t think that “cxx” has the same strong association with C++.

2 Likes

Quoting @Max_Desiatov from this pull request on SwiftPM:

Usually, CPP is used as an abbreviation for the C preprocessor, while CXX more commonly refers to the C++ compiler.

Also, Clang internally uses the “cxx” prefix when referring to C++ things.

1 Like

That’s a very specialised use of “usually”, which doesn’t clearly motivate exposing this spelling convention to language users.

2 Likes

FWIW, in macOS and Linux CLI cpp command will launch a C preprocessor, not a C++ compiler.

There's already a precedent for this in public SwiftPM API: CXXSetting type and cxxSettings property of Target, introduced in Swift 5.0. Our users (at least those interested in C++) have already been exposed to this naming convention for more than 3 and a half years.

Either way, I personally would like this thread to stay focused on overall progress with interop. Naming convention discussions, especially those dedicated to a specific API, deserve their own separate thread.

9 Likes

Thanks for the interesting update. As someone who is writing (network protocol) C++ code for microcontrollers which I really would love to reuse from Swift, I wonder about a general rough timeframe. I see that you're working on this since quite a while and though I enjoy the progress updates, I'm not enough "into" the problem to realize how far you really are.

So… for calling a C++ library that is using bits and pieces from STL, how much of an effort is left over until we can just import that like we do with C libraries? Are we talking months, a year, or multiple years?

2 Likes

(post deleted by author)

I cannot provide a specific answer. However, over the next couple of Swift releases we're aiming to be in a position where this becomes possible for a subset of an API surface of any existing C++ library, including parts of STL itself as well.

Right now it's possible to try this feature in an experimental manner, so you could probably import large chunks of the library you're interested in into Swift already. Are you interested in potentially becoming an early adopter so that you could try to see if our current experimental support could work for your use case? That could help us refine the initially supported version of interop as well.

2 Likes

Is it already possible to iterate over std::unordered_map and std::unordered_set from Swift?

I expected that something like this will compile. But it didn't work in Xcode 14.2 with -enable-experimental-cxx-interop

// typedef std::unordered_set<long> IntSet;

extension IntSet: Sequence {
  public struct Iterator: IteratorProtocol {
    private var current: IntSet.const_iterator
    private let end: IntSet.const_iterator

    init(begin: IntSet.const_iterator, end: IntSet.const_iterator) {
      current = begin
      self.end = end
    }

    public mutating func next() -> Int? {
      guard current != end else { return nil }

      defer { current += 1 }

      return current.pointee
    }
  }

  public func makeIterator() -> Iterator {
    return Iterator(begin: begin(), end: end())
  }
}

It should be far easier than you've thought — with just a single line:

extension IntSet: CxxSequence {}

This is already implemented on main, and is planned for 5.8. I guess it may be missing from 5.7 though.

2 Likes

At the moment you also need to manually conform the const_iterator to the UnsafeCxxInputIterator as well:

import Cxx

extension SetOfCInt.const_iterator : UnsafeCxxInputIterator { }
extension SetOfCInt : CxxSequence { }

But yes, on main (will become swift-5.8) we support iteration over std::map / std::set. We already have a test case for map, but I added one for set just now too: [interop] add a testcase for std::set iteration in Swift by hyp · Pull Request #62627 · apple/swift · GitHub

2 Likes

I managed to do interop from Swift to C++ but I can't seem to do the opposite.

Where can I find a minimal example to call a Swift function from C++ with instruction on project setup using Swift 5.7.2 (or other toolchain) and Xcode 14.2 ?

I guess there must be a flag like -emit-cxx-header or something else to pass to the Swift compiler to get the required c++ header ?

The compiler flag is called -enable-experimental-cxx-interop. See project setup and user guide. Both of these links are in the original post.

I used this flag with success to experiment when calling C++ from Swift.
However problem is calling Swift function from C++.

I experimented with a very simple project consisting of a minimal Swift library and C++ code as documented in the user guide and using -enable-experimental-cxx-interop but with no success. The generated header fille don't include the c++ function signature.

Anybody having success calling a simple Swift function from C++ ?

Update: It's better using -Xfrontend -enable-experimental-cxx-interop with @_expose(Cxx) and dropping -clang-header-expose-decls=all-public.

I can use Swift from C++ in Xcode with the latest development snapshot toolchain. I use -Xfrontend -clang-header-expose-decls=all-public in the "Build Settings - Other Swift Flags" setting. As the name suggests, make sure the access levels of your Swift functions and types are public; otherwise they won't show up in the generated header.

Hope it will help :)

2 Likes

Thanks a lot Tongjie, that works ! The public function now appear in the header without the need to use the @_expose attribute.

My very basic experiment calling Swift from C++ now work ok on macOS.

Ultimately I expect the interop to work also on Windows and Linux.
I tried on Windows and I can't see any header generated.
The build ran ok but I there is no header generated at all.

This is my Swift package for the test:

// swift-tools-version: 5.7

import PackageDescription

let package = Package(
    name: "SwiftCXXInteropExample",
    products: [
        .library(name: "MySwiftLib", type: .dynamic, targets: ["MySwiftLib"])
    ],
    dependencies: [],
    targets: [
        .target(name: "MySwiftLib", swiftSettings: [.unsafeFlags(["-enable-experimental-cxx-interop", "-Xfrontend", "-clang-header-expose-decls=all-public"])])
    ]
)

Any hint to generate the header on Windows ?

This work also on Windows !!
I just have to add the -emit-objc-header flag.

2 Likes

Would you mind to share code and how to set up the project? Thanks.

Edit: OK, there was a link to a description, but a full working example is always great of course.

Stefan, following my related experimental code.
I tested ok using the Swift 5.8 development snapshot and also the trunk.

Package.swift

// swift-tools-version: 5.7

import PackageDescription

let package = Package(
    name: "SwiftCXXInteropExample",
    products: [
        .library(name: "MySwiftLib", type: .dynamic, targets: ["MySwiftLib"])
    ],
    dependencies: [],
    targets: [
        .target(name: "MySwiftLib", swiftSettings: [.unsafeFlags(["-enable-experimental-cxx-interop", "-emit-objc-header", "-Xfrontend", "-clang-header-expose-decls=all-public"])])
    ]
)

MySwiftLib/file.swift

// Ultra simple Swift library
// file.swift

import Foundation

// Expose a Swift function to C++
// The function must be public

// To expose _all_ the public functions, use the flag -Xfrontend -clang-header-expose-decls=all-public
// The @_expose attribute can be used to expose the function using a different name, useful when dealing with function overloads
// ref: https://github.com/apple/swift/blob/main/docs/CppInteroperability/UserGuide-CallingSwiftFromC%2B%2B.md

// Note: in the UserGuide this attribute looks like @expose(C++, foo) however,
// at least in the 5.8 and latest toolchain snapshot we must instead write @_expose(Cxx, "foo")

//@_expose(Cxx, "my_exposed_swift_add")
public func my_swift_add(a: Int64, b: Int64) -> Int64 {
    return a + b
}

$ swift build

2 Likes