Improving the usability of C libraries in Swift
Recorded: Jan. 23, 2026, noon
| Original | Summarized |
Improving the usability of C libraries in Swift | Swift.org
Docs Community Packages Blog Install (6.2.3) Docs Community Packages Blog Install (6.2.3) Improving the usability of C libraries in Swift Doug Gregor Doug Gregor is a member of the Language Steering Group and works on the Swift compiler and runtime. January 22, 2026 There are many interesting, useful, and fun C libraries in the software ecosystem. While one could go and rewrite these libraries in Swift, usually there is no need, because Swift provides direct interoperability with C. With a little setup, you can directly use existing C libraries from your Swift code. The C library here that Swift is using comes from the webgpu-headers project, which vends a C header (webgpu.h) that is used by several implementations of WebGPU. WebGPU is a technology that enables web developers to use the system’s GPU (Graphics Processing Unit) from the browser. For the purposes of this post, you don’t really need to know anything about WebGPU: I’m using it as an example of a typical C library, and the techniques described in this blog post apply to lots of other well-designed C libraries. These same annotations can be used for any C library to provide a safer, more ergonomic development experience in Swift without changing the C library at all. Note: Some of what is covered in this post requires bug fixes that first became available in Swift 6.2.3. Setup: Writing a module map A module map is a way of layering a Swift-friendly modular structure on top of C headers. You can create a module map for the WebGPU header by writing the following to a file module.modulemap: The easiest thing to do is to put module.modulemap alongside the header itself. For my experiment here, I put it in the root directory of my webgpu-headers checkout. If you’re in a Swift package, put it into its own target with this layout: If you reference this WebGPU target from elsewhere in the package, you can import WebGPU to get access to the C APIs. There are a few ways to see what the Swift interface for a C library looks like. The swift-synthesize-interface tool in Swift 6.2+ prints the Swift interface to the terminal. Let’s do it from the command line, using swift-synthesize-interface. From the directory containing webgpu.h and module.modulemap, run: The leading xcrun and the -sdk argument with the path is only needed on macOS; on other platforms, make sure swift-synthesize-interface is in your path. The -target operation is the triple provided if you run swiftc -print-target-info. It looks like this: The output of swift-synthesize-interface is the Swift API for the WebGPU module, directly translated from C. For example, this code from the WebGPU header: is translated into: public var WGPUAdapterType_DiscreteGPU: WGPUAdapterType { get } and there are lots of global functions like this: It’s a starting point! You can absolutely write Swift programs using these WebGPU APIs, and they’ll feel a lot like writing them in C. Let’s see what we can do to make it better. C enums can be used for several things. Sometimes they really represent a choice among a number of alternatives. Sometimes they represent flags in a set of options, from which you can choose several. Sometimes they’re just a convenient way to create a bunch of named constants. Swift conservatively imports enum types as wrappers over the underlying C type used to store values of the enum (e.g., WGPUAdapterType wraps a UInt32) and makes the enumerators into global constants. It covers all of the possible use cases, but it isn’t nice. This works, and results in a much nicer Swift API: Now, we get an enum that we can switch over, and nice short case names, e.g., That’s great, but I already broke my rule: no header modifications unless I have to! The problem of needing to layer information on top of existing C headers is not a new one. As noted earlier, Swift relies on a Clang feature called API notes to let us express this same information in a separate file, so we don’t have to edit the header. In this case, we create a file called WebGPU.apinotes (the name WebGPU matches the module name from module.modulemap), which is a YAML file describing the extra information. We’ll start with one that turns WGPUAdapterType into an enum: Tags here is a term used in the C and C++ standard to refer to enum, struct, union, or class types. Any information about those types in the API notes file will go into that section. We’ll be adding more to this API notes file as we keep digging through the interface. The WebGPU header has a number of “object” types that are defined like this: This gets imported into Swift as an alias for an opaque pointer type, which is… not great: WebGPU object types are reference counted, and each object type has corresponding AddRef and Release functions to increment and decrement the reference count, like this: Of course, you can use these functions in Swift exactly how you do in C, making sure to balance out calls to AddRef and Release, but then it would be every bit as unsafe as C. Now, WGPUBindGroup gets imported like this: The extra typealias is a little unexpected, but overall this is a huge improvement: Swift is treating WGPUBindGroup as a class, meaning that it automatically manages retains and releases for you! This is both an ergonomic win (less code to write) and a safety win, because it’s eliminated the possibility of mismanaging these instances. “ReturnedWithOwnership” here means that the result of the call has already been retained one extra time, and the caller is responsible for calling “release” when they are done with it. The <swift/bridging> header has a SWIFT_RETURNS_RETAINED macro that expresses this notion. One can use it like this: Now, Swift will balance out the retain that wgpuDeviceCreateBindGroup has promised to do by performing the extra release once you’re done using the object. Once these annotations are done, we’re all set with a more ergonomic and memory-safe API for this C library. There’s no need to ever call wgpuBindGroupRelease or wgpuBindGroupAddRef yourself. That makes WGPUBindGroupImpl import as a class type, with the given retain and release functions. We can express the “returns retained” behavior of the wgpuDeviceCreateBindGroup function like this: That’s enums and classes, so now let’s tackle… functions. A typical function from webgpu.h, like this: will come into Swift like this: Note that _ on each parameter, which means that we won’t use argument labels for anything when we call it: That matches C, but it isn’t as clear as it could be in Swift. Let’s clean this up by providing a better name in Swift that includes argument labels. We can do so using SWIFT_NAME (also in <swift/bridging>), like this: Within the parentheses, we have each of the argument labels that we want (or _ meaning “no label”), each followed by a :. This is how one describes a full function name in Swift. Once we’ve made this change to the Swift name, the C function comes into Swift with argument labels, like this: That makes the call site more clear and self-documenting: Importing functions as methods There is more usable structure in this API. Note that the wgpuQueueWriteBuffer function takes, as its first argument, an instance of WGPUQueue. Most of the C functions in WebGPU.h are like this, because these are effectively functions that operate on their first argument. In a language that has methods, they would be methods. Swift has methods, so let’s make them methods! There are three things to notice about this SWIFT_NAME string: It starts with WGPUQueueImpl., which tells Swift to make this function a member inside WGPUQueueImpl. Note that this also requires WGPUQueue(Impl) to be imported as a class, as we did earlier for WGPUBindGroupImpl. Once we’ve done so, we get a much-nicer Swift API: We’ve hacked up the header again, but didn’t have to. In WebGPU.apinotes, you can put a SwiftName attribute on any entity. For wgpuQueueWriteBuffer, it would look like this (in the Functions section): Importing functions as properties WebGPU.h has a number of Get functions that produce information about some aspect of a type. Here are two for the WGPUQuerySet type: With the SWIFT_NAME tricks above, we can turn these into “get” methods on WGPUQuerySet, like this: That’s okay, but it’s not what you’d do in Swift. Let’s go one step further and turn them into read-only computed properties. To do so, use the getter: prefix on the Swift name we define. We’ll skip ahead to the YAML form that goes into API notes: And now, we arrive at a nice Swift API: Importing functions as initializers SWIFT_NAME can also be used to import a function that returns a new instance as a Swift initializer. For example, this function creates a new WGPUInstance (which we assume is getting imported as a class like we’ve been doing above): We can turn this into a Swift initializer, which is used to create a new object, using the same SWIFT_NAME syntax but where the method name is init. Here is the YAML form that goes into API notes: and here is the resulting Swift initializer: Now, one can create a new WGPUInstance with the normal object-creation syntax, e.g., Another Boolean type? The WebGPU header defines its own Boolean type. I wish everyone would use C99’s _Bool and be done with it, but alas, here are the definitions for WebGPUs Boolean types: This means that WGPUBool will come in to Swift as a UInt32. The two macros aren’t available in Swift at all: they’re “too complicated” to be recognized as integral constants. Even if they were available in Swift, it still wouldn’t be great because we want to use true and false for Boolean values in Swift, not WGPU_TRUE and WGPU_FALSE. Now, we get WGPUBool imported like this: To be able to use true and false literals with this new WGPUBool, we can write a little bit of Swift code that makes this type conform to the ExpressibleByBooleanLiteral protocol, like this: That’s it! Better type safety (you cannot confuse a WGPUBool with any other integer value) and the convenience of Boolean literals in Swift. webgpu.h describes a set of flags using a typedef of the WGPUFlags type (a 64-bit unsigned integer) along with a set of global constants for the different flag values. For example, here is the WGPUBufferUsage flag type and some of its constants: Similar to what we saw with WGPUBool, WGPUBufferUsage is a typedef of a typedef of a uint64_t. There’s no type safety in this C API, and one could easily mix up these flags with, say, those of WGPUMapMode: We can do better, by layering more structure for the Swift version of this API using the same SwiftWrapper approach from WGPUBool. This goes into the Typedefs section of API notes: Now, WGPUBufferUsage comes in as its own struct: The initializers let you create a WGPUBufferUsage from a WGPUFlags value, and there is also a rawValue property to get a WGPUFlags value out of a WGPUBufferInstance, so the raw value is always there… but the default is to be type safe. Additionally, those global constants will come in as members of WGPUBufferUsage, like this: /** /** /** This means that, if you’re passing a value of type WPUBufferUsage, you can use the shorthand “leading dot” syntax. For example: setBufferUsage(._MapRead) Swift has dropped the common WPUBufferUsage prefix from the constants when it made them into members. However, the resulting names aren’t great. We can rename them by providing a SwiftName in the API notes file within the Globals section: We can go one step further by making the WGPUBufferUsage type conform to Swift’s OptionSet protocol. If we revise the API notes like this: Now, we get the nice option-set syntax we expect in Swift: Nullability Throughout webgpu.h, the WGPU_NULLABLE macro is used to indicate pointers that can be NULL. The implication is that any pointer that is not marked with WGPU_NULLABLE cannot be NULL. For example, here is the definition of wgpuCreateInstance we used above: The WGPU_NULLABLE indicates that it’s acceptable to pass a NULL pointer in as the descriptor parameter. Clang already has nullability specifiers to express this information. We could alter the declaration in the header to express that this parameter is nullable but the result type is never NULL, like this: This eliminates the implicitly-unwrapped optionals (!) from the signature of the initializer, so we end up with one that explicitly accepts a nil descriptor argument and always returns a new instance (never nil): Now, I did cheat by hacking the header. Instead, we can express this with API notes on the parameters and result type by extending the entry we already have for wgpuCreateInstance like this: To specific nullability of pointer parameters, one can identify them by position (where 0 is the first parameter to the function) and then specify whether the parameter should come into Swift as optional (O, corresponds to _Nullable), non-optional (N, corresponds to _Nonnull) or by left unspecified as an implicitly-unwrapped optional (U, corresponds to _Null_unspecified). For the result type, it’s a little different: we specified the result type along with the nullability specifier, i.e., WGPUInstance _Nonnull. The end result of these annotations is the same as the modified header, so we can layer nullability information on top of the header. webgpu.h is about 6,400 lines long, and is regenerated from a database of the API as needed. Each of the WebGPU implementations seems to augment or tweak the header a bit. So, rather than grind through and manually do annotations, I wrote a little Swift script to “parse” webgpu.h, identify its patterns, and generate WebGPU.apinotes for most of what is discussed in this post. The entirety of the script is here. It reads webgpu.h from standard input and prints WebGPU.apinotes to standard output. // Object definitions, marked by WGPU_OBJECT_ATTRIBUTE. // Function declarations, marked by WGPU_FUNCTION_ATTRIBUTE That’s enough to identify all of the enum types (so we can emit the EnumExtensibility: closed API notes), object types (to turn them into shared references), and functions (which get nicer names and such). The script is just a big readLine loop that applies the regexes to capture all of the various types and functions, then does some quick classification before printing out the API notes. The resulting API notes are in WebGPU.apinotes, and the generated Swift interface after these API notes are applied is here. You can run it with, e.g., This script full of regular expressions is, admittedly, a bit of a hack. A better approach for an arbitrary C header would be to use libclang to properly parse the headers. For WebGPU specifically, the webgpu-headers project contains a database from which the header is generated, and one could also generate API notes directly from that header. Regardless of how you get there, many C libraries have well-structured headers with conventions that can be leveraged to create safer, more ergonomic projections in Swift. The techniques described in this post can be applied to just about any C library. To do so, I recommend setting up a small package like the one described here for WebGPU, so you can iterate quickly on example code to get a feel for how the Swift projection of the C API will work. The annotations might not get you all the way to the best Swift API, but they are a lightweight way to get most of the way there. Feel free to also extend the C types to convenience APIs that make sense in Swift, like I did above to make WGPUBool conform to ExpressibleByBooleanLiteral. The regular structure of webgpu.h helped considerably when trying to expose the API nicely in Swift. That said, there are a few ways in which webgpu.h could be improved to require less annotation for this purpose: WGPU_ENUM_ATTRIBUTE would be slightly nicer if placed on the enum itself, rather than on the typedef. If it were there, we could use WGPU_OBJECT_ATTRIBUTE could provide the names of the retain and release operations and be placed on the struct itself. If it were there, we could use WGPU_NULLABLE could be placed on the pointer itself (i.e., after the *) rather than at the beginning of the type, to match the position of Clang’s nullability attributes. If it were placed there, then Continue Reading What's new in Swift: December 2025 Edition Welcome to the latest digest of news from the Swift project.
Docs Community Packages Blog Install Tools Xcode Visual Studio Code Emacs Neovim Other Editors Community Overview Swift Evolution Diversity Mentorship Contributing Governance Code of Conduct License Security Color scheme preference Light Dark Auto Copyright © 2026 Apple Inc. All rights reserved. Swift and the Swift logo are trademarks of Apple Inc. Privacy Policy Cookies API |
Improving the usability of C libraries in Swift Doug Gregor There are many interesting, useful, and fun C libraries in the software ecosystem. While one could go and rewrite these libraries in Swift, usually there is no need, because Swift provides direct interoperability with C. With a little setup, you can directly use existing C libraries from your Swift code. When you use a C library directly from Swift, it will look and feel similar to using it from C. That can be useful if you’re following sample code or a tutorial written in C, but it can also feel out of place. For example, here’s a small amount of code using a C API: ```swift The C library here that Swift is using comes from the webgpu-headers project, which vends a C header (webgpu.h) that is used by several implementations of WebGPU. WebGPU is a technology that enables web developers to use the system’s GPU (Graphics Processing Unit) from the browser. For the purposes of this post, you don’t really need to know anything about WebGPU: I’m using it as an example of a typical C library, and the techniques described in this blog post apply to lots of other well-designed C libraries. The Swift code above has a very “C” feel to it. It has global function calls with prefixed names like wgpuInstanceCreateSurface and global integer constants like WGPUStatus_Error. It pervasively uses unsafe pointers, some of which are managed with explicit reference counting, where the user provides calls to wpuXYZAddRef and wgpuXYZRelease functions. It works, but it doesn’t feel like Swift, and inherits various safety problems of C. Fortunately, we can improve this situation, providing a safer and more ergonomic interface to WebGPU from Swift that feels like it belongs in Swift. More importantly, we can do so without changing the WebGPU implementation: Swift provides a suite of annotations that you can apply to C headers to improve the way in which the C APIs are expressed in Swift. These annotations describe common conventions in C that match up with Swift constructs, projecting a more Swift-friendly interface on top of the C code. In this post, I’m going to use these annotations to improve how Swift interacts with the WebGPU C code. By the end, we’ll be able to take advantage of Swift features like argument labels, methods, enums, and automatic reference counting, like this: ```swift These same annotations can be used for any C library to provide a safer, more ergonomic development experience in Swift without changing the C library at all. Note: Some of what is covered in this post requires bug fixes that first became available in Swift 6.2.3. Setup: Writing a module map A module map is a way of layering a Swift-friendly modular structure on top of C headers. You can create a module map for the WebGPU header by writing the following to a file module.modulemap: ```swift The easiest thing to do is to put module.modulemap alongside the header itself. For my experiment here, I put it in the root directory of my webgpu-headers checkout. If you’re in a Swift package, put it into its own target with this layout: ``` If you reference this WebGPU target from elsewhere in the package, you can import WebGPU to get access to the C APIs. Seeing the results There are a few ways to see what the Swift interface for a C library looks like. The swift-synthesize-interface tool in Swift 6.2+ prints the Swift interface to the terminal. Xcode’s “Swift 5 interface” counterpart to the webgpu.h header will show how the header has been mapped into Swift. Let’s do it from the command line, using swift-synthesize-interface. From the directory containing webgpu.h and module.modulemap, run: `xcrun swift-synthesize-interface -I . -module-name WebGPU -target arm64-apple-macos15 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX26.0.sdk` The leading `xcrun` and the `-sdk` argument with the path is only needed on macOS; on other platforms, make sure swift-synthesize-interface is in your path. The `-target` operation is the triple provided if you run `swiftc -print-target-info`. It looks like this: ```json The output of `swift-synthesize-interface` is the Swift API for the WebGPU module, directly translated from C. For example, this code from the WebGPU header: ```c is translated into: ```swift public var WGPUAdapterType_DiscreteGPU: WGPUAdapterType { get } and there are lots of global functions like this: ```swift It’s a starting point! You can absolutely write Swift programs using these WebGPU APIs, and they’ll feel a lot like writing them in C. Let’s see what we can do to make it better. Cleaning up enumeration types C enums can be used for several things. Sometimes they really represent a choice among a number of alternatives. Sometimes they represent flags in a set of options, from which you can choose several. Sometimes they’re just a convenient way to create a bunch of named constants. Swift conservatively imports enum types as wrappers over the underlying C type used to store values of the enum (e.g., WGPUAdapterType wraps a UInt32) and makes the enumerators into global constants. It covers all of the possible use cases, but it isn’t nice. The WGPUAdapterType enum really is a choice among one of several options, which would be best represented as an enum in Swift. If we were willing to modify the header, we could apply the `enum_extensibility(closed)` attribute to the enum, like this: ```c This works, and results in a much nicer Swift API: ```swift Now, we get an enum that we can switch over, and nice short case names, e.g., ```swift That’s great, but I already broke my rule: no header modifications unless I have to! API notes The problem of needing to layer information on top of existing C headers is not a new one. As noted earlier, Swift relies on a Clang feature called API notes to let us express this same information in a separate file, so we don’t have to edit the header. In this case, we create a file called WebGPU.apinotes (the name WebGPU matches the module name from module.modulemap), which is a YAML file describing the extra information. We’ll start with one that turns WGPUAdapterType into an enum: ```yaml This tells Swift to represent WGPUAdapterType as a structure instead of a raw C enum. Put WebGPU.apinotes alongside the header itself, and now WGPUAdapterType gets mapped into a Swift enum: ```swift To be able to use true and false literals with this new WGPUAdapterType, we can write a little bit of Swift code that makes this type conform to Swift’s `ExpressibleByBooleanLiteral` protocol, like this: ```swift Scripting the creation of WebGPU.apinotes The regular structure of webgpu.h helped considerably when trying to expose the API nicely in Swift. That said, there are a few ways in which webgpu.h could be improved to require less annotation for this purpose: * `WGPU_ENUM_ATTRIBUTE` could be placed on the enum itself, rather than on the typedef. If it were there, we could use `#define WGPU_ENUM_ATTRIBUTE __attribute__((enum_extensibility(closed)))` and not have to generate any API notes to bring these types in as proper enums in Swift. ```swift Continue Reading |