Swift & C: What I Learned Building Swift Bindings to libgit2

I've spent the last few weekends working on Gift, Swift bindings to libgit2. It was a really fun introduction to using C from Swift, which isn't always easy. Here are some of the things I learned.

Interacting with C Pointers from Swift

It's a good thing many people have already written on this subject. I found the following links extremely helpful:

C Pointers in libgit2

Most functions in libgit2 return an error code, and take a pointer as an argument. If the error code is 0, we know the function succeeded in populating the pointer with a value:

/**
 * Parse a SHA, like "f9e7a", into an object ID.
 *
 * @param out A pointer to a struct, of type `git_oid`, 
*             that the result is written to.
 * @param str input The SHA to convert.
 * @return 0, or an error code.
 */
int git_oid_fromstr(git_oid *out, const char *str);

The Swift signature for this function is:

func git_oid_fromstr(
  out: UnsafeMutablePointer<git_oid>, 
  str: UnsafePointer<Int8>) -> Int32

Note that it takes an UnsafeMutablePointer<git_oid>, meaning:

The str parameter is of type UnsafePointer<Int8>, but thanks to Swift's ability to interoperate with C, we can just pass this function a Swift String. Read the Apple blog article linked above for details.

Here's how we can create the pointer and use the function:

// Allocate the pointer. We need to deallocate it
// after we're done, or else our program will 
// never free the memory used here.
var out = UnsafeMutablePointer<git_oid>.alloc(1)

let errorCode = git_oid_fromstr(out, "f9e7a")
if errorCode == 0 {
  // ...the function succeeded, we may use the pointer here.
  println("git_oid struct: \(out.memory)")
} else {
  // ...the function failed.
}

// We'd better not forget to deallocate the pointer!
out.dealloc(1)

Opaque Pointers

The example above referenced git_oid. libgit2 defines this struct and its members in an exported, public header file:

/** Unique identity of any object (commit, tree, blob, tag). */
typedef struct git_oid {
  /** raw binary formatted id */
  unsigned char id[20];
} git_oid;

But some structs aren't fully defined in header files, like git_repository:

// types.h
typedef struct git_repository git_repository;

The actual members of git_repository are declared in a header file that isn't exported as a public header, repository.h. That means we can't use the symbol git_repository from our Swift code:

// Does not compile!
// Error: "Use of undeclared type 'git_repository'".
let repository: UnsafeMutablePointer<git_repository>

Instead, to get a pointer to a git_repository struct, we need to use COpaquePointer. For example, here's a libgit2 function that opens a repository:

/**
 * Open a git repository.
 *
 * @param out Pointer to a repository struct pointer.
 * @param path The path to the repository.
 * @return 0, or an error code.
 */
int git_repository_open(git_repository **out, const char *path);

Here's the Swift signature for that function:

func git_repository_open(
  out: UnsafeMutablePointer<COpaquePointer>, 
  path: UnsafePointer<Int8>) -> Int32

And here's how we can use that function from Swift:

// Create a pointer to an opaque type.
var out = COpaquePointer.null()

let errorCode = git_repository_open(&out, "path/to/repository")
if errorCode == 0 {
  // ...the function succeeded. We may use the pointer.
} else {
  // ...the function failed.
}

Using Opaque Pointers

An opaque pointer is sort of useless–we can't call pointer.memory to access the struct that's being pointed to. But libgit2 defines functions that, given an opaque pointer to a repository, provide information on that repository. Here's the Swift signature of one such function:

/**
 * Get the path of the repository represented by
 * the opaque pointer.
 */
func git_repository_path(repo: COpaquePointer) -> String

And here's an example of how to use it:

// let repository: COpaquePointer
let path = String.fromCString(git_repository_path(repository))

Using Class Deinitializers to Free Pointers

Pointers that are allocated must, at some point, be freed. One trick to make sure pointers are freed when no longer needed is to wrap them in a class, and free the pointer in the class's deinit method:

public class Repository {
  internal let cRepository: COpaquePointer

  internal init(cRepository: COpaquePointer) {
    self.cRepository = cRepository
  }

   deinit {
     git_repository_free(cRepository)
   }
}

When the repository object above is no longer referenced anywhere, it is deallocated, which frees our pointer as well. Some caveats:

C Callback Functions and CFunctionPointer

libgit2 is written in platform-independent C, and so can't make use of the blocks we know and love from Objective-C. Instead, it takes pointers to functions to implement a callback API:

typedef int (*git_tag_foreach_cb)(
  const char *name,
  git_oid *oid, 
  void *payload);

/**
 * Executes the callback function for each tag
 * in a repository.
 *
 * @param repo The repository.
 * @param callback The callback function.
 * @param payload Pointer to arbitrary data that
 *                is passed to the callback function.
 */
int git_tag_foreach(
  git_repository *repo,
  git_tag_foreach_cb callback,
  void *payload);

Here's the Swift signature for this function:

typealias git_tag_foreach_cb = CFunctionPointer<(
  (
    UnsafePointer<Int8>, 
    UnsafeMutablePointer<git_oid>, 
    UnsafeMutablePointer<Void>
  ) -> Int32
)>

func git_tag_foreach(
  repo: COpaquePointer,
  callback: git_tag_foreach_cb,
  payload: UnsafeMutablePointer<Void>) -> Int32

Unfortunately, in Interacting with C Pointers in Swift, @develtima explains that we can't get a usable CFunctionPointer to a function defined in Swift.

So instead, Gift defines Objective-C helper functions that take a Swift closure, and execute that closure from within a C callback function:

// The type of the callback closure
// we will use in Swift.
typedef int (^GIFTTagForEachCallback)(const char *name,
                                      git_oid *objectID);

// Gift wraps `git_tag_foreach`, which takes a
// CFunctionPointer, and instead takes a
// closure/block as an argument.
int gift_tagForEach(git_repository *repository,
                    (GIFTTagForEachCallback)callback) {
  // Call the libgit2 function `git_tag_foreach`, passing
  // a pointer to a staic C function we define below.
  // We pass the closure as the "payload" parameter.
  return git_tag_foreach(repository,
                         gift_callback,
                         (__bridge void *)callback);
}

// The static C function libgit2 will call.
static int gift_callback(const char *name,
                         git_oid *oid,
                         void *payload) {
  // We grab the closure we passed as
  // the "payload" parameter, then execute it.
  GIFTTagForEachCallback block = (__bridge id)payload;
  return block(name, oid);
}

It's impossible to wrap C API's that take CFunctionPointers entirely in Swift, but I think the above approach is the next best thing.