Use clang API notes to Annotate C Header Files for Swifty Module Imports

I learned threee new things when I read Doug Gregor’s “Improving the usability of C libraries in Swift” blog post that may also affect your daily life importing C libraries:

  1. The clang compiler support YAML module annotations so you don’t have to own and modify .h files;
  2. The SWIFT_NAME annotation can be used to make free C functions act like methods on objects;
  3. You can tell the compiler to generate object types for automatic reference counting instead of dealing with opaque pointers.

Until today, I was under the impression that you can make C functions sound a little bit nicer and add argument labels with SWIFT_NAME mostly, but this is something on a different level.

One Example to turn a free function into a method:[#20260123webgpu][]

WGPU_EXPORT void wgpuQueueWriteBuffer(
      WGPUQueue queue, WGPUBuffer buffer, uint64_t bufferOffset, void const * data, size_t size) 
  WGPU_FUNCTION_ATTRIBUTE SWIFT_NAME("WGPUQueueImpl.writeBuffer(self:buffer:bufferOffset:data:size:)");

That’s what got me interested: object-oriented conventions from C, expressed in Swift’s notation! Where does the type come from? Usually, you just get opaque pointers.

The WGPUQueueImpl typealias is generated as well with a different annotation – that’s now becoming a reference-counting wrapper around WGPUQueue instead of an opaque pointer. The heavy lifting of this is done by a parameterized annotation, SWIFT_SHARED_REFERENCE(increment, decrement), to which you pass function names to increment and decrement the refcount.

The annotation, if you would apply it manually in the header, would look like this:

// Original typedef is:
//   typedef struct WGPUQueueImpl* WGPUQueue WGPU_OBJECT_ATTRIBUTE;
typedef struct 
  SWIFT_SHARED_REFERENCE(wgpuGroupAddRef, wgpuGroupRelease)
  WGPUQueueImpl* WGPUQueue WGPU_OBJECT_ATTRIBUTE;

… making the generated Swift interface:

// Original generated typealias would've been:
//   public typealias WGPUGroup = OpaquePointer
public class WGPUGroupImpl { }
public typealias WGPUGroup = WGPUGroupImpl

Now the secret ingredient to make this bearable is to not copy the header files, but to use a YAML “sidecar” file to instruct clang to generate a different API. Combining the generated type declaration and the method example from above:

- Name: WGPUGroupImpl
  SwiftImportAs: reference
  SwiftReleaseOp: wgpuGroupRelease
  SwiftRetainOp: wgpuGroupAddRef
- Name: wgpuQueueWriteBuffer
  SwiftName: WGPUQueueImpl.writeBuffer(self:buffer:bufferOffset:data:size:)

clang supports API notes in a YAML file to annotate .h files without having to change the .h files themselves for module imports. That’s so neat: with that you can wrap C libraries in Swift without having to maintain a copy of the headers.

  • API notes are found under the name “Foo.apinotes” for a module named “Foo”.
  • Private module map API notes are picked up as well: “Foo_private.apinotes”
  • Here’s an example file from the clang docs.

clang docs:

Clang will search for API notes files next to module maps only when passed the -fapinotes-modules option.

Next to methods, properties also work with SWIFT_NAME annotations:

- Name: wgpuQuerySetGetCount
  SwiftName: getter:WGPUQuerySetImpl.count(self:)
- Name: wgpuQuerySetGetType
  SwiftName: getter:WGPUQuerySetImpl.type(self:)

Depending on the C API, this can be a game-changer in terms of Swift language ergonomics for you and your team. No more manual refcounting (which, if limited to e.g. a function scope, isn’t that bad thanks to defer, but can get hairy when you need to escape and keep references alive for longer), proper Swift types instead of opaque pointers, and methods and properties on these types instead of namespaced free functions. Lovely.

Always Write Functions to Cope with all Possible Parameter Values

Matt Galagher is back writing at Cocoa with Love. His goal is maintainability, which is the greatest of all, I think. It’s easy to copy code samples together to create an app, bur it’s hard to create a product you can keep alive and make better over years. In that vein, his first article, “Partial functions in Swift, Part 1: Avoidance”, includes a lot of details why partial functions will hurt you. This is a great topic. Read his post for the basic set theory involved.

Continue reading …

Parameter Objects Simplify Your API Versions

I watched an AltConf about API design the other day. I cannot seem to find which talk it was, though. Anyware, the presenter talked about using parameter objects when the parameter list grows too long or is open for change in future versions. Parameter objects can change internally and evolve with the API. You can add or remove attributes, for example, while the API calls of old client code don’t have to change: they still pass the same type in. That makes framework updates a bit less painful because method signatures stay the same.

Continue reading …