Bringing Package Management to Swift's C++ Interoperability
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
lunasvgremains visible in Swift. Document::loadFromDatabecomes a static method.documentis thestd::unique_ptr<Document>thatloadFromDatareturns, the smart pointer that owns the C++ object. Swift reaches the object it owns throughpointee, the same role->plays in the C++ version above.renderToBitmapreturns a C++Bitmapby value.writeToPngremains 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:
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_ptrin this example must remain alive while Swift accessespointee, 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.