Getting started with Swift-C++ interop
Tags: swift, cpp, interop
Published: 2022-12-14 15:20:17

Photo by <a href="https://unsplash.com/@garciasaldana_?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Pablo García Saldaña</a> on <a href="https://unsplash.com/s/photos/two-way-sign?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>.

Playing around with Swift toolchain experimental features.

## Intro

Swift is a very comfortable language. It has some quirks and a learning curve, but ultimately you can ship production-ready code with it pretty fast. However, sometimes you have performance-critical sections and Swift just doesn't cut it. In such cases, a popular choice is using C++. And the question arises "how do I call this C++ func from Swift"? Usually, you have to write [an Objective-C wrapper that will act as a public interface for your C++ code](https://ksemianov.github.io/articles/cpp2swift/). And Swift toolchain can import Objective-C declarations to Swift. The main limitation is that you cannot use C++ classes in Objective-C, only simple POD structs.

We'll write a [Sieve of Eratosthenes](https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes) algorithm with both C++ and Swift. Then learn how to enable C++ interop, call C++ code from Swift, and compare implementation performance. Keep in mind that the feature is experimental and subject to changes. This publication compiles on Xcode Version 14.2

## Algorithm

The Sieve of Eratosthenes finds all prime numbers less than or equal N. Prime number is an integer that's divisible only by itself and 1. The algorithm creates a boolean array to indicate if each number is prime. And progressively iterates over them, marking all multiples as not prime.

Here is the Swift implementation.

```swift
// primes.swift

func primes(n: Int) -> [Int] {
    var isPrime = [Bool](repeating: true, count: n + 1)

    for value in stride(from: 2, to: n + 1, by: 1) where isPrime[value] {
        if value * value > n { break }

        for multiple in stride(from: value * 2, to: n + 1, by: value) {
            isPrime[multiple] = false
        }
    }

    var result = [Int]()

    for value in stride(from: 2, to: n + 1, by: 1) where isPrime[value] {
        result.append(value)
    }

    return result
}
```

For C++ we need a header and a source file. Note, that we `typedef` to have a cleaner name for referring to `std::vector<long>`. 

```cpp
// primes.hpp

#include <vector>

typedef std::vector<long> VectorLong;

VectorLong primes(const long &n);
```

```cpp
// primes.cpp
#include <algorithm>

#include "primes.hpp"

VectorLong primes(const long &n) {
    std::vector<char> isPrime(n + 1); // faster than std::vector<bool>
    std::fill(isPrime.begin(), isPrime.end(), true);

    for (long value = 2; value * value <= n; ++value) {
        if (!isPrime[value]) { continue; }

        for (long multiple = value * 2; multiple <= n; multiple += value) {
            isPrime[multiple] = false;
        }
    }

    VectorLong result;

    for (long value = 2; value <= n; ++value) {
        if (!isPrime[value]) { continue; }

        result.push_back(value);
    }

    return result;
}
```

## Project structure

![Project structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5mn23z8aeb1qb3njnuwy.png)

We'll do a Swift Package with two separate targets to hold our Swift and C++ code. To import C++ code from Swift, we need a modulemap.

```cpp
// module.modulemap

module CXX {
    header "CXX.hpp"
    requires cplusplus
}

// CXX.hpp

#include "primes.hpp"
```

And do not forget to pass `-enable-experimental-cxx-interop` to the Swift target in `Package.swift`.

```swift
// swift-tools-version: 5.7

import PackageDescription

let package = Package(
    name: "SwiftCXXInteropExample",
    platforms: [
        .macOS(.v12),
    ],
    products: [
        .library(name: "CXX", targets: ["CXX"]),
        .executable(name: "CLI", targets: ["CLI"])
    ],
    dependencies: [],
    targets: [
        .target(name: "CXX"),
        .executableTarget(
            name: "CLI",
            dependencies: ["CXX"],
            swiftSettings: [.unsafeFlags(["-enable-experimental-cxx-interop"])]
        )
    ]
)
```

See the doc from Apple for more info on [how to enable C++ interop](https://github.com/apple/swift/blob/main/docs/CppInteroperability/GettingStartedWithC%2B%2BInterop.md).

## Trying out

It's much easier to use our `VectorLong` from Swift with conformance to `RandomAccessCollection` and, happily, it's really easy to do.

```swift
import CXX

extension VectorLong: RandomAccessCollection {
    public var startIndex: Int { 0 }
    public var endIndex: Int { size() }
}
```

Now we can call our C++ function from Swift and print the results to the console.

```swift
let cxxVector = primes(100)
let swiftArray = [Int](cxxVector)
print(swiftArray)
```

Let's see if our C++ implementation actually performs faster.

```swift
let signposter = OSSignposter()

let count = 100
let n = 10_000_000

for _ in 0..<count {
    let state = signposter.beginInterval("C++")
    let _ = primes(n)
    signposter.endInterval("C++", state)
}

for _ in 0..<count {
    let state = signposter.beginInterval("Swift")
    let _ = primes(n: n)
    signposter.endInterval("Swift", state)
}
```

![Metrics](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/joaw9knwdwgs64j9epml.png)

Slightly faster with an average duration of 26 ms vs. 28 ms for Swift. 

## Final thoughts

We were able to directly use `std::vector` in Swift. I personally haven't found a convenient way to round-trip between Swift `Map` and `std::map`, `Set` and `std::set`. Nevertheless, C++ interop is rapidly developing and the future seems bright.

[CppInteroperability](https://github.com/apple/swift/tree/main/docs/CppInteroperability) folder in the Swift repo contains more information on interop features, limitations and plans.

See the full code at [https://github.com/ksemianov/SwiftCXXInteropExample](https://github.com/ksemianov/SwiftCXXInteropExample)