If you’ve ever wanted to call a C++ library directly from Swift, you’ve probably ended up writing a C facade or an Objective-C++ wrapper first. Swift has been able to import C and Objective-C APIs since its early releases, but its C importer couldn’t represent C++ features such as namespaces, overloaded functions, templates, constructors, destructors, and standard-library types, so a wrapper was the only way in.

Swift 5.9 changed that by introducing direct C++ interoperability: Swift can now import C++ headers and call supported C++ APIs with no wrapper layer in between.

To try this with an existing package, we use LunaSVG, a C++ SVG rendering library packaged in ConanCenter. The application creates an SVG scene in Swift and asks LunaSVG to render it to a PNG.

Swift knows how to call supported C++ APIs, while Conan provides LunaSVG, its transitive dependencies, and the information needed to compile and link the application. A small Clang module map makes LunaSVG’s headers importable from Swift.

The complete project is available in the Conan examples repository:

cxx_interop/
├── conanfile.py
├── demo.xcodeproj/
├── main.swift
├── ci_test_example.py
└── README.md

Starting with the C++ API

Before looking at Swift, it helps to see how the LunaSVG API would normally be used from C++. Given svg and css as std::string values holding an SVG document and a stylesheet, and output as the destination PNG path, rendering and writing the file looks like this:

auto document = lunasvg::Document::loadFromData(svg);
document->applyStyleSheet(css);

auto bitmap = document->renderToBitmap();
bitmap.writeToPng(output);

Although short, this fragment already touches several C++ features: a namespace, a static method, a std::unique_ptr<Document>, member functions, and a Bitmap returned by value.

Making the C++ API Visible to Swift

Before Swift can call this API, it needs to know which C++ header to import and the module name it should use. Swift gets this information through a Clang module map.

module LunaSVGMod {
    header "/path/to/include/lunasvg/lunasvg.h"
    export *
}

This gives the LunaSVG header a module name, LunaSVGMod. Swift must then be compiled with C++ interoperability enabled and the module-map option is forwarded to the Clang importer used by the Swift compiler:

-cxx-interoperability-mode=default
-Xcc -fmodule-map-file=/path/to/lunasvg.modulemap

-Xcc passes the following option to the Clang instance embedded in the Swift compiler. With these options, Swift can import LunaSVGMod and use the supported declarations from the header. The Xcode configuration shown later adds the same options using the module map generated by Conan.

Note: Despite the similar terminology, this is a Clang module, not a named C++20 module. Swift does not currently import C++20 modules.

Calling the Same API from Swift

With the C++ standard library and LunaSVGMod imported, main.swift calls the same API to render the demo scene:

import CxxStdlib
import LunaSVGMod

let svg = "<svg>...</svg>"
let css = ".sky{fill:#8ECBEB} ..."

let document = lunasvg.Document.loadFromData(std.string(svg))
document.pointee.applyStyleSheet(std.string(css))

let bitmap = document.pointee.renderToBitmap()
_ = bitmap.writeToPng(std.string("summer.png"))

svg and css are ordinary Swift strings.

The mapping is visible in the code:

  • The C++ namespace lunasvg remains visible in Swift.
  • Document::loadFromData becomes a static method.
  • document is the std::unique_ptr<Document> that loadFromData returns, the smart pointer that owns the C++ object. Swift reaches the object it owns through pointee, the same role -> plays in the C++ version above.
  • renderToBitmap returns a C++ Bitmap by value.
  • writeToPng remains an ordinary member-function call.

The std.string(...) conversions are explicit because Swift does not automatically bridge a dynamic Swift String to std::string. Constructing the C++ string allocates and copies the string data.

Running this produces the actual LunaSVG output, summer.png:

summer.png: LunaSVG rendering of the demo scene with the summer stylesheet applied

Putting It All Together

Conan feeds Xcode through two generators, XcodeDeps and XcodeToolchain, which turn the dependency graph into a set of .xcconfig files an Xcode project can use as its build configuration.

The generators provide the dependency and C++ toolchain settings, but they do not add the Swift-specific interoperability options required by this target. The -cxx-interoperability-mode flag and the module map still have to reach swiftc, through OTHER_SWIFT_FLAGS, the build setting Xcode passes straight to the Swift compiler. XcodeToolchain exposes build_settings, a plain dict of build settings to add to the .xcconfig file it generates. The consumer recipe’s generate() sets it alongside the module map from earlier, while layout() collects everything the generators write into a generators folder:

def layout(self):
    self.folders.generators = "generators"

def generate(self):
    XcodeDeps(self).generate()

    include_dir = self.dependencies["lunasvg"].cpp_info.includedir
    header = f"{include_dir}/lunasvg/lunasvg.h"

    modulemap_path = os.path.join(self.generators_folder, "lunasvg.modulemap")
    modulemap = textwrap.dedent(f'''\
        module LunaSVGMod {{
            header "{header}"
            export *
        }}
        ''')
    save(self, modulemap_path, modulemap)

    cppstd = cppstd_flag(self)

    tc = XcodeToolchain(self)
    tc.build_settings["OTHER_SWIFT_FLAGS"] = (
        f'$(inherited) -cxx-interoperability-mode=default '
        f'-Xcc {cppstd} -Xcc -fmodule-map-file="{modulemap_path}"'
    )
    tc.generate()

cppstd_flag turns the profile’s compiler.cppstd into the matching -std= flag, so the headers are parsed with the same standard as the rest of the build. $(inherited) keeps any Swift flags the project already defines. build_settings needs Conan 2.32 or newer.

The Xcode project uses generators/conan_config.xcconfig as the Base Configuration for its Release configuration, which makes everything Conan generates — include paths, linker options, and the Swift flags above — available to the target.

Install the dependency, then open the project. Please use Conan 2.32 or newer.

conan install . -s build_type=Release --build=missing
open demo.xcodeproj

From there it is a normal Xcode project: press Run, and Swift calls into LunaSVG. The same build also works from the command line:

xcodebuild -project demo.xcodeproj -scheme demo -configuration Release \
    -derivedDataPath build build
./build/Build/Products/Release/demo

Running the executable writes summer.png to the working directory.

Together, Conan, Xcode, and Swift’s C++ interoperability make an unmodified ConanCenter package directly callable from Swift, without a wrapper library. Direct access, however, does not remove the usual binary compatibility requirements of calling C++ code.

The Limits of Direct Interoperability

The module map exposes LunaSVG’s declarations, but Swift still calls a compiled C++ library through the platform C++ ABI. The headers and binary therefore need to agree on the target, compiler ABI, standard library, dependencies, and any option that changes public declarations. Conan’s package model is useful here: it selects or builds the native artifact that sits behind the imported API.

Direct interop also does not turn a C++ API into a Swift-safe API. The most important points to keep in mind are:

  • C++ ownership and lifetime rules still apply. The std::unique_ptr in this example must remain alive while Swift accesses pointee, and raw pointers or views require the same care they would in C++.
  • Swift cannot catch C++ exceptions. A C++ exception that crosses the boundary terminates the program.
  • Swift supports a growing subset of C++, not every template or standard-library type. The status page documents the current limitations.

For libraries with unsupported constructs, exception-heavy APIs, or difficult lifetime rules, a small wrapper can still provide a cleaner boundary. The difference is that it is now a design choice rather than a requirement for every C++ library.

Note: SwiftPM can also distribute C++ libraries for use through Swift’s C++ interoperability and is a convenient choice when a suitable package already exists. Here, Conan consumes LunaSVG and its transitive dependencies from ConanCenter without requiring the C++ dependency graph to be repackaged for SwiftPM. The Conan recipes reuse the libraries’ upstream build systems rather than rewriting their configuration and platform logic in Package.swift.

Try the complete example and check the official Swift C++ interoperability guide for the complete mapping and safety rules.

Happy coding!

This post was written with AI assistance and reviewed by humans.