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:
- Interacting with C Pointers, from Apple's Swift blog
- Using Legacy C APIs with Swift, by @jparishy
- Interacting with C Pointers in Swift, by @develtima
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:
- It's a pointer to a
git_oid
struct. - It's mutable, because the function will create a
git_oid
struct, then set the pointer to point to the newly created struct. - It's unsafe, because Swift cannot guarantee that the memory pointed to by the pointer is valid. That is, our program may crash when accessing that location.
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:
- We need to be sure two instances of this class are not created with the same pointer–that would lead to a double-free.
- In libgit2, commits and other objects require that the repository they belong to still exists as long as they do. So the repository object must not be cleaned up while those objects are still in use.
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.