# Using C Libraries in Swift: Importing and Calling C APIs

## Pro Summary

Swift imports C through Clang modules, so a module map is what turns headers into something you can `import`: SwiftPM generates one for a C target's `include/` directory, and `.systemLibrary` targets use a hand-written `module.modulemap` plus `pkgConfig`. The importer maps types mechanically. `int` becomes `Int32` and `size_t` becomes `Int`. `const T *` becomes `UnsafePointer<T>`, which is implicitly unwrapped unless the header says `_Nonnull` or `_Nullable`, and pointers to incomplete structs become `OpaquePointer`. Plain C enums become `RawRepresentable` structs with global constants, and anything Swift can't express, such as function-like macros and variadic functions, is dropped. Lifetimes are your job: pointers from `withCString`, the `withUnsafe…` APIs and implicit `&array` bridging are valid only for the duration of the call.

```
// Package.swift
.target(name: "CFoo"),                    // Sources/CFoo/include/*.h -> module map generated
.systemLibrary(name: "CSQLite", pkgConfig: "sqlite3",
               providers: [.apt(["libsqlite3-dev"]), .brew(["sqlite"])]),
.executableTarget(name: "App", dependencies: ["CFoo", "CSQLite"]),

// Sources/CSQLite/module.modulemap
module CSQLite [system] {
    header "shim.h"      // #include <sqlite3.h>
    link "sqlite3"
    export *
}
```

Requires: Examples verified with Swift 6.4 and tools-version 6.0 (Swift 6 language mode).

Gotchas:

- A closure passed as a C function pointer cannot capture anything. The error "a C function pointer cannot be formed from a closure that captures context" appears only in a full compile, not with `-typecheck`, so pass state through the API's `void *` context with `Unmanaged.passUnretained(obj).toOpaque()` and keep `obj` alive while callbacks can still fire.
- In the Swift 6 language mode, reading or writing a mutable C global such as `optind` is an error ("not concurrency-safe because it involves shared mutable state"). `@preconcurrency import` silences it but doesn't make concurrent access safe, so wrap the global in a C accessor or confine it to one actor.
- In the Swift 6 mode, the array overloads `String(cString: [CChar])` and `String(validatingCString:)` are deprecated; call the pointer overload inside `withUnsafeBufferPointer`, or trim the NUL and use `String(decoding:as:)` or `String(validating:as:)`.
- Plain `typedef enum { FooRed, FooGreen }` imports as a struct with global constants, so a `switch` needs `default`; add `__attribute__((enum_extensibility(closed)))` to get a real Swift enum with prefix-stripped cases (`.small`, `.large`).
- Function-like macros (`#define SQUARE(x) ((x)*(x))`) are not imported, while constant macros are (`#define LIMIT 64` becomes `Int32` and a string-literal macro becomes `String`), so expose function-like macros through a `static inline` function in the header, which Swift can call.

Primary sources:

- [Wrapping C/C++ Library in Swift (swift.org)](https://www.swift.org/documentation/articles/wrapping-c-cpp-library-in-swift.html)
- [Using Imported C Functions in Swift (Apple)](https://developer.apple.com/documentation/swift/using-imported-c-functions-in-swift)
- [C Interoperability (Apple)](https://developer.apple.com/documentation/swift/c-interoperability)
- [Clang Modules and module map language](https://clang.llvm.org/docs/Modules.html)

Source: https://devdocs10.com/articles/swift/swift-using-c-libraries  
By Murat Bo · Updated 2026-10-17 · DevDocs10 (https://devdocs10.com)

Swift can work with C libraries directly, which lets you reuse proven system APIs, vendor SDKs, and low-level utility code without rewriting everything in Swift. This article shows how C imports work, how Swift maps C types into native Swift types, and how to avoid the most common interoperability mistakes.

> **Quick answer:** To use a C library in Swift, make the C headers available to your build, import the module with `import`, and call the C API through Swift’s imported names. Swift converts many C types automatically, but pointers, buffers, and ownership rules still require careful handling.

> **Difficulty:** Intermediate
>
> **You'll understand this better if you know:** basic Swift syntax, how functions and types work, and the difference between values and pointers.

## 1. What Is Using C Libraries in Swift?

Using a C library in Swift means calling functions, reading constants, and working with structs, enums, and pointers defined in C from Swift code. Swift imports the C declarations and presents them in a Swift-friendly way whenever possible.

- C functions become callable Swift functions.
- C constants and macros may import as Swift constants or values.
- C structs import as Swift structs. A plain C enum imports as a struct with a raw value plus one global constant per case; only enums declared with `NS_ENUM` or `__attribute__((enum_extensibility(...)))` import as Swift enums.
- C pointers import as `UnsafePointer` or `UnsafeMutablePointer` types when Swift cannot safely copy the data.

This is not the same as writing C code in Swift. You are still following the C library’s rules for memory ownership, nullability, and buffer lifetime.

## 2. Why Using C Libraries Matters

C libraries are everywhere: operating system APIs, graphics libraries, compression libraries, math routines, networking code, and existing app dependencies. Swift interoperability lets you reuse them instead of replacing them.

It matters because C often provides:

- Stable APIs that have been used for years.
- Fast access to low-level system features.
- Small, reusable libraries that may not exist in native Swift form.
- A migration path when a project already depends on C code.

You should use a C library from Swift when it solves a real problem and the API is already available. If a native Swift package exists and gives you the same result with safer types, that is often the better choice.

## 3. Basic Syntax

Most C interoperability starts with importing the module and calling its functions like normal Swift symbols. The exact import depends on how the C library is packaged.

### Importing a C module

When a library is exposed as a Clang module, Swift imports it with a plain `import` statement. The C standard library itself works this way: it is the `Darwin` module on Apple platforms and `Glibc` on most Linux distributions.

```swift
#if canImport(Darwin)
import Darwin
#elseif canImport(Glibc)
import Glibc
#endif
```

After import, Swift can see the module’s public C declarations. For system libraries, the module name is often provided by the platform SDK. For your own C code, the module is usually created with a module map or a build setting.

### Calling a C function

A C function typically looks like a normal Swift function call after import:

```swift
import Foundation
```

```swift
import Darwin

let value = labs(-42)
```

Here, Swift imports the C function `labs` (declared in C as `long labs(long)`) and lets you call it directly. On 64-bit Apple and Linux platforms, C `long` maps to Swift `Int`, so the result is a normal Swift `Int`.

## 4. Step-by-Step Examples

### Example 1: Calling a simple C function

A common starting point is a function that takes and returns plain scalar values. This is the easiest case because Swift can bridge the values directly.

```swift
import Darwin

let rawNumber = -17
let absoluteValue = labs(rawNumber)

// absoluteValue is 17
```

This works because `labs` takes and returns a C `long`, which Swift imports as `Int`, and it does not require manual memory management. Be careful with `abs`: calling `abs` with an `Int` resolves to the Swift standard library’s generic `abs`, not the C function, which takes and returns `Int32`.

### Example 2: Working with a C struct

C structs often import as Swift value types. You can create them, pass them to C, and read fields directly.

```swift
import CoreGraphics

var point = CGPoint(x: 12, y: 24)
point.x = 18
```

Swift imports many common C structs, such as `CGPoint`, in a way that feels native. This is one of the biggest conveniences of interoperability.

### Example 3: Passing a Swift string to a C API

C APIs often expect a pointer to a null-terminated sequence of bytes, while Swift `String` is a higher-level type. Swift provides helper APIs to bridge safely when possible.

```swift
import Darwin

let message = "hello"
message.withCString { cString in
    let length = strlen(cString)
    // length is 5
}
```

The closure guarantees the C string pointer stays valid only for the duration of the call. That temporary lifetime is important for safe interoperability.

### Example 4: Using an output parameter

Some C functions fill in values through pointers rather than returning them. Swift can pass a variable as an in-out pointer when the C function expects a writable location.

```swift
import Darwin

var now = timeval()
let result = gettimeofday(&now, nil)

if result == 0 {
    print("Seconds since 1970: \(now.tv_sec)")
}
```

Writing `&now` passes a pointer to `now` for the duration of the call, and `gettimeofday` fills in its fields. In real code, you will often use `withUnsafeMutablePointer` or `withUnsafeMutableBytes` when an API needs memory you control.

## 5. Practical Use Cases

- Calling operating system APIs that have no Swift wrapper.
- Using existing compression, crypto, or image processing libraries.
- Integrating a vendor SDK distributed as C headers and a compiled library.
- Reusing a C utility library already shared with another project.
- Accessing file descriptors, sockets, and other low-level system resources.

These cases are common in tooling, platform code, and performance-sensitive applications.

## 6. Common Mistakes

### Mistake 1: Importing the header file instead of the module

Swift does not usually import a raw C header with a file path the way C does. The C declarations need to be exposed as a module that Swift can understand.

**Problem:** This creates a module lookup problem because Swift cannot find a file-based header import in normal Swift source.

```swift
import "mylib.h"

let result = library_function()
```

**Fix:** Import the module name that your build system exposes.

```swift
import MyCLibrary

let result = library_function()
```

The corrected version works because Swift can import Clang modules, not arbitrary quoted header paths.

### Mistake 2: Treating a C pointer like a Swift value

C APIs often return pointers that may be null, temporary, or owned by the caller. You must not assume they behave like regular Swift values.

**Problem:** This code would crash or fail because a raw pointer is not automatically a valid Swift object.

```swift
import Darwin

let namePointer = getenv("HOME")
let name = namePointer.uppercased()
```

**Fix:** Unwrap the pointer and convert it to a Swift string safely.

```swift
import Darwin

if let namePointer = getenv("HOME") {
    let name = String(cString: namePointer)
    let display = name.uppercased()
}
```

The fixed version works because it respects the optional pointer and converts the C string into a Swift `String` before using string methods.

### Mistake 3: Letting a C buffer escape its lifetime

Many helper methods provide a temporary buffer that exists only inside a closure. Saving that pointer and using it later is unsafe.

**Problem:** The pointer becomes invalid after the closure ends, so later reads can produce bad data or crashes.

```swift
import Foundation

var savedPointer: UnsafePointer<CChar>?
"hello".withCString { pointer in
    savedPointer = pointer
}
let text = String(cString: savedPointer!)
```

**Fix:** Use the pointer only inside the closure or copy the data into owned Swift storage.

```swift
import Foundation

"hello".withCString { pointer in
    let text = String(cString: pointer)
    // Use text here
}
```

The corrected version works because the pointer is consumed only while it is guaranteed to remain valid.

## 7. Best Practices

### Prefer module imports over ad hoc bridging

When possible, expose your C code as a module so Swift can import it cleanly. This makes the API easier to reuse and reduces build configuration surprises.

```swift
import MyCLibrary
```

A module import is more maintainable than manually juggling header search paths in every target.

### Convert C strings into Swift strings early

Swift code is easier to read and safer when you convert C strings as soon as you receive them.

```swift
if let cString = getenv("PATH") {
    let path = String(cString: cString)
}
```

This avoids passing raw pointers deeper into your Swift codebase.

### Use the smallest unsafe scope possible

Unsafe APIs are sometimes necessary, but the unsafe code should be isolated to a narrow section of your program.

```swift
var bytes: [UInt8] = [1, 2, 3]
bytes.withUnsafeMutableBytes { rawBuffer in
    // Pass rawBuffer.baseAddress to a C function if needed
}
```

This keeps the dangerous part of the code focused and easier to audit.

## 8. Limitations and Edge Cases

- Not every C macro imports cleanly; some macros are not visible to Swift unless they can be represented as constants or functions.
- Function-like macros often do not import as callable Swift functions.
- Nullability matters: a C pointer may map to an optional Swift pointer.
- Ownership is manual for many APIs, so you must know whether the caller or callee frees memory.
- Thread-safety is not guaranteed by Swift just because the code is imported; you still need to follow the C library’s rules.
- Some APIs depend on platform-specific SDKs, so code that compiles on macOS may not build unchanged on Linux or Windows.

A frequent search phrase is that an imported API is “not working” when the real issue is missing module exposure, unsupported macros, or incorrect ownership assumptions.

## 9. Practical Mini Project

Let’s build a tiny Swift command-line tool that uses a C function to inspect the current process ID and read an environment variable safely. This is small, but it demonstrates module import, optional pointers, and string conversion in one place.

```swift
import Darwin

let processID = getpid()
print("Process ID: \(processID)")

if let homePointer = getenv("HOME") {
    let homeDirectory = String(cString: homePointer)
    print("Home directory: \(homeDirectory)")
} else {
    print("Home directory not found.")
}
```

This example shows the typical shape of C interop in Swift: import the module, call the function, unwrap optional pointers, and immediately convert raw data into a Swift type.

## 10. Key Points

- Swift can call C libraries directly when the C API is exposed as an importable module.
- Many C types bridge naturally into Swift, but pointers still require explicit handling.
- Use Swift string and collection APIs as early as possible to reduce unsafe code.
- Know the library’s memory ownership rules before you pass buffers or receive pointers.
- Most interoperability problems come from module setup, lifetime mistakes, or incorrect pointer usage.

## 11. Practice Exercise

- Import a C module available on your system, such as a platform SDK module.
- Call one scalar-returning function and one function that returns a pointer.
- Convert any returned C string into a Swift `String`.
- Print both results without leaking raw pointers outside their safe scope.

**Expected output:** Two lines of text showing the function result and the converted string.

**Hint:** Choose functions that return simple values first, then add pointer-based APIs only after you can verify the import works.

```swift
import Darwin

let pid = getpid()
print("PID: \(pid)")

if let pathPointer = getenv("PATH") {
    let path = String(cString: pathPointer)
    print("PATH: \(path)")
} else {
    print("PATH not set")
}
```

## 12. Final Summary

Using C libraries in Swift is one of the most practical interoperability features in the language. It lets you reuse mature APIs, call platform functions, and connect to existing C-based systems while still writing the rest of your app in Swift.

The main skill is not learning a separate syntax, but learning how Swift maps C concepts into safer forms. If you understand modules, pointers, optional values, and buffer lifetimes, you can use C APIs confidently without making your Swift code unsafe.

Next, practice with one small C API on your target platform and focus on translating raw pointers into Swift values as early as possible.

## Where AI Coding Assistants Get This Wrong

- Often suggested: Calls `printf("%d\n", value)` (or other C variadic functions) directly from Swift.
  Instead: Use `print`, `String(format:)` from Foundation, or the `va_list` variant through `withVaList([value]) { vprintf("%d\n", $0) }`. (Swift imports C variadic functions as unavailable ("'printf' is unavailable: Variadic function is unavailable").)
- Often suggested: Passes a closure that captures local variables or `self` to a C callback parameter such as `qsort` or a library's `on_event(cb, ctx)`.
  Instead: Use a non-capturing closure, and pass state through the `void *` context: `Unmanaged.passUnretained(obj).toOpaque()` on the way in and `Unmanaged<T>.fromOpaque(ctx!).takeUnretainedValue()` inside the callback (use `passRetained`/`release()` if the C side outlives your reference). (C function pointers carry no context, and the compiler rejects capturing closures.)
- Often suggested: Copies `import Darwin` from macOS tutorials into cross-platform code, or writes `#if os(Linux) import Glibc`.
  Instead: Choose the libc module with a `canImport` chain (`Darwin`, then `Glibc`, then `Musl`, then `ucrt` on Windows), or import Foundation, which re-exports the platform C library. (`Darwin` doesn't exist off Apple platforms, and the Static Linux SDK provides `Musl` instead of `Glibc`.)
