Interoperability with C, Objective-C and C++

This section documents the Swift 6 language mode as shipped by Swift 6.3, as published in The Swift Programming Language at docs.swift.org, which is the reference these pages are written and verified against. 6.4-beta-only features are always flagged as such — never presented as baseline.

This content was generated with the assistance of AI and should be verified against docs.swift.org before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

Swift was designed from the start to sit alongside C and Objective-C, and more recently gained first-class C++ interop as well. All three follow the same underlying idea — a compiler-generated importer maps foreign declarations into their nearest Swift equivalent — but each language’s importer has its own rules, gaps, and annotations for smoothing over the differences.

The Clang Importer: C into Swift

When a Swift target imports a C header (directly, or via a bridging header — see Objective-C Interop below), the Clang importer translates it, largely following the same conventions Objective-C’s own importer uses for its NS-prefixed types:

C construct Imported as

int, double, _Bool

Int32, Double, Bool — fixed-width, never the platform-dependent int.

struct Point { double x, y; }

Point, a Swift struct with the same fields, memberwise init synthesized.

union

A Swift struct exposing every member at the same memory offset — reading one after writing another is undefined behavior, exactly as in C.

typedef

A Swift typealias (or, for NS_ENUM-style patterns, a real Swift enum).

T *

UnsafeMutablePointer<T> (or UnsafePointer<T> for a const T ); T * becomes a pointer to a pointer type the same way.

void (*)(int) (function pointer)

A Swift closure type, (Int32) → Void.

#define MAX_SIZE 128

A global let constant, when the importer can infer a type; more complex macros may not import at all.

Preprocessor conditionals (#if/#ifdef)

Resolved by Clang before Swift ever sees the header — Swift never sees the macro itself, only the branch Clang picked.

// Point.h:  struct Point { double x, y; };  double distance(struct Point a, struct Point b);

var origin = Point(x: 0, y: 0)               // memberwise init, synthesized by the importer
let d = distance(origin, Point(x: 3, y: 4))   // free C functions import as free Swift functions

See C: Structures, Unions and Type Aliases and C: Pointers for these constructs on the C side, and Memory Safety and Unsafe Pointers for working with the Unsafe*Pointer family an imported C API hands back.

Module Maps and System-Library Targets in SwiftPM

A plain C header has no notion of a Swift "module" — a module map (module.modulemap) is what tells the Clang importer which headers belong to which module and how to expose them:

// Sources/CZlib/module.modulemap
module CZlib [system] {
    header "shim.h"
    link "z"
    export *
}
// Package.swift
let package = Package(
    name: "MyTool",
    targets: [
        .systemLibrary(name: "CZlib", pkgConfig: "zlib"),   // wraps a C library already on the system
        .target(name: "MyTool", dependencies: ["CZlib"])
    ]
)

A systemLibrary target wraps a C library that is expected to already be installed (via pkgConfig, or a providers list naming the system package manager’s package name), rather than one SwiftPM builds itself; an ordinary .target with a Sources/<Name>/include/.h layout works the same way for a C library that *is built from source as part of the package. See Swift Package Manager for package targets in general.

Objective-C Interop

Mechanism Purpose

Bridging header (<Target>-Bridging-Header.h)

Exposes chosen Objective-C headers to every Swift file in an app target, with no import needed on the Swift side.

@objc / @objcMembers

Exposes a Swift declaration (or every member of a class) to Objective-C, which requires inheriting from NSObject.

NS_SWIFT_NAME

Renames an Objective-C declaration as seen from Swift.

Nullability (nonnull/nullable, NS_ASSUME_NONNULL_BEGIN/END)

Becomes T vs. T? on the Swift side —  an unaudited header imports as implicitly-unwrapped optionals throughout.

Lightweight generics (NSArray<NSString *> *)

Becomes a properly parameterized Swift collection ([String]) instead of [Any].

- (BOOL)op:(NSError **)error

Imported as func op() throws — the error out-parameter becomes a thrown Error.

// Objective-C: - (void)fetchUserWithID:(NSInteger)userID
//                  completion:(void (^)(User * _Nullable, NSError * _Nullable))completion;

let user = try await service.fetchUser(id: 42)   // imported as an `async throws` function -- no completion handler

An Objective-C API following the completion:(void (^)(Result, NSError )) pattern is imported as an async function automatically, unifying it with native Swift concurrency (see Async/Await and Tasks) with no manual bridging. Exposing Swift *to Objective-C is the mirror image: a class must inherit NSObject and mark members @objc (or @objcMembers), and the compiler emits a <Module>-Swift.h header for Objective-C files to #import.

This reference’s Objective-C Reference covers this direction in much greater depth, in particular Swift Interoperability (the bridging header and generated header mechanics, the full table of imported constructs, and mixed-language project practices) and Lightweight Generics and Nullability (the annotations that produce a clean Swift API).

C++ Interop

// Package.swift
.target(
    name: "MySwiftTarget",
    swiftSettings: [.interoperabilityMode(.Cxx)]     // enables -cxx-interoperability-mode=default
)

C++ interop is opt-in per target, via -cxx-interoperability-mode=default (or .interoperabilityMode(.Cxx) in a SwiftPM manifest). Once enabled:

// Point.hpp:  struct Point { double x, y; double distance(const Point& other) const; };

var origin = Point()
origin.x = 0
origin.y = 0
let other = Point(x: 3, y: 4)                // {cpp} constructors are imported too
origin.distance(other)                       // member functions become Swift methods

var vec = std.vector<Int32>()                // std::vector<int> imported as a Swift-usable generic type
vec.push_back(1)
vec.push_back(2)
for value in vec { print(value) }             // std::vector conforms to Swift's Sequence when interop is on
C++ construct Imported as

A C++ class/struct

A Swift struct or class exposing its public members and methods.

References (T&, const T&)

Mapped to Swift’s own parameter-passing conventions; a const T& parameter behaves like an implicit borrow.

std::vector, std::string, std::map, …​

Usable directly from Swift, with Sequence/Collection conformance synthesized where the shape allows it.

C++ templates

Only concrete instantiations actually used from Swift are imported — an uninstantiated template has nothing for Swift to bind to.

Exposing Swift to C++ works in the same target, once interop is enabled — a C++ file can #include the generated Swift header and call exposed Swift functions and methods directly, though (as of Swift 6.3) the Swift-to-C++ direction supports a narrower set of Swift features than the C++-to-Swift direction does. See C++: Templates and C++: Containers for these constructs on the C++ side.

This reference’s C++ Reference and its own Memory Management and Smart Pointers page cover ownership and lifetime rules relevant to values crossing the boundary.

The Three References, Side by Side

How Swift, C, C++ and Objective-C see each other within one build target: the Clang importer maps C, module maps expose system libraries, the bridging header and generated header connect Objective-C, and -cxx-interoperability-mode connects C++

For C, C++ and Objective-C in their own right — rather than as seen through Swift’s importer — see:

See Also