With open-source Swift fast approaching, soon we’ll be running our Swift code on platforms that won’t have Apple’s libraries on them. So how will we use fundamental techniques, like sorting an array quickly? In this talk from #Pragma 2015, Chris Eidhof demonstrates how to wrap a C library (!) from Swift, enabling Swift developers to use the C standard library on all sorts of platforms.
Introduction (0:00)
We’re going to look at the interaction between C and Swift using some light programming. More specifically, I want to look at working with C APIs in Swift. We’ll start by doing quicksort.
We’re going to use quicksort from the C Standard Library. In general, this is a bad idea because there are built-in sorting functions in Swift, and you should use them because they’re optimized and nice to work with. This is just an example of how to work with the C API. Don’t use this version of quicksort. Let’s make an array of numbers and sort it. Normally in Swift, you would do something like this:
let numbers = [3,1,2,17,4]
print(numbers.sort())
You can also use sortInPlace()
which modifies the array.
var numbers = [3,1,2,17,4]
numbers.sortInPlace()
print(numbers)
qsort (3:05)
Now, we’re going to make it a little more complicated and use the C function. Let’s write qsort
. After running man qsort
in the terminal, we get:
void
qsort(void *base, size_t nel, size_t width, int (*compar)(const void *, const void *));
void
is the return type of the qsort
function. It doesn’t return anything. This quicksort is also modifying the original array. The qsort
function takes one parameter, as the first parameter, and that’s a void
pointer to base. This is the C way of saying, “This is an array and anything can be in here.” If you had an integer array, it would say int
pointer to base, but this is a void
pointer because it can be anything. The second parameter is nel
and is the number of elements in the array. Arrays in C have to have a pointer to the first element and then the number of elements. The third parameter is the width, which is the size of a single element in memory. If you iterate, you have to increase by the width. Lastly, the forth parameter is a comparison function and it’s going to take two const void
pointers and return an integer. These are the pointers to the elements as this function is used to compare two elements in the array. The elements are passed as a const void
pointer and that means it’s a pointer to anything. The const
means you cannot change the elements; it’s const
ant. This star (*
) means it’s a C function pointer and you can use these function pointers in Swift.
var numbers = [2, 1, 17, 3]
qsort(&numbers, numbers.count, sizeOf(Int)) { (l, r) -> Int32 in
let left: Int = UnsafePointer(l).memory
let right: Int = UnsafePointer(r).memory
if left < right { return -1 }
if left == right {return 0 }
return 1
}
The first thing needs to be a pointer to the first element of the array. In Swift, they made it very easy where we can write, &numbers
. The second parameter is the number of elements in your array. We can say, numbers.count
. Then, we need the size of a single element. If you’re used to C, you know this function, sizeof()
. This is incorrect, but we will see this later on. The forth is a comparison function where we need to compare two numbers. The block takes two parameters, the left element and the right element. It needs to return an Int32. Let’s compare two elements. We got a sorted array using the built-in quicksort.
Let’s wrap this up into a nice function so that we can use it over and over again.
func quicksort(var input: [Int]) -> [Int] {
qsort(&input, input.count, sizeOf(Int)) { (l, r) -> Int32 in
let left: Int = UnsafePointer(l).memory
let right: Int = UnsafePointer(r).memory
if left < right { return -1 }
if left == right {return 0 }
return 1
}
return input
}
How do we make it work for strings? The easy way is to copy-paste it and change some things:
func quicksort(var input: [String]) -> [String] {
qsort(&input, input.count, sizeOf(String)) { (l, r) -> Int32 in
let left: String = UnsafePointer(l).memory
let right: String = UnsafePointer(r).memory
if left < right { return -1 }
if left == right {return 0 }
return 1
}
return input
}
qsort_b (18:40)
This is a lot of work. If we want to create a quicksort of another type, we’ll have to copy-paste it again. Let’s try to make this thing generic by using Swift generics. There’s a second version of quicksort, qsort_b
.
void
qsort_b(void *base, size_t nel, size_t width, int (^compar)(const void *, const void *));
Let’s go over the type. qsort_b
takes a base void
pointer. This is the pointer to the first element of the array. It takes a number of elements, the width, and a comparison function pointer. However, you can see a tiny detail if you look closely. We had a star in qsort
. In qsort_b
, we have a carat (^
). This means it is a block. If you have a block in Objective-C, and also in Swift and in C, you can refer to things outside of the closure. The following code will work using qsort_b
:
func quicksort<A: Comparable>(var input: [A]) -> [A] {
qsort_b(&input, input.count, sizeOf(A)) { (l, r) -> Int32 in
let left: A = UnsafePointer(l).memory
let right: A = UnsafePointer(r).memory
if left < right { return -1 }
if left == right {return 0 }
return 1
}
return input
}
Now, we have a quicksort function that’s generic. It works on any A
that’s comparable. We could say, “We’re done here.” However, we need to make it more complicated. The quicksort has a block-based variant, but many of the C APIs you will work with will not have block-based APIs. They will work with function pointers. There is an idiom in C that is used to pass context to function pointers.
qsort_r (20:25)
If you’re wrapping any other library in C, you will probably not have this block-based API. You need to solve the problem in a third way. This is the third version of quicksort.
void
qsort_r(void *base, size_t nel, size_t width, void *thunk, int (*compar)(void *, const void *, const void *));
We have the same base pointers. We have the number of elements, the width, and a void pointer called thunk
. The comparison function also changed. It’s of type compar
and it takes a void pointer, and only then it takes two constant void pointers. thunk
is an additional argument and it’s passed as the first argument to the function, pointed to compar
. We can put anything in this thunk
pointer and we get it back inside our comparison function. This is how it works in many C APIs. Instead of using this qsort_b
, I want to use qsort_r
, and then pass the comparison function as our thunk
.
extension Comparable {
static func compare(l: UnsafePointer<Void>, _ r: UnsafePointer<Void>) -> Int32 {
let left: Self = UnsafePointer(l).memory
let right: Self = UnsafePointer(r).memory
if left < right { return -1 }
if left == right { return 0 }
return 1
}
}
typealias CompareFunc = (UnsafePointer<Void>, UnsafePointer<Void>) -> Int32
func cmp(thunk: UnsafeMutablePointer<Void>, l: UnsafePointer<Void>, r: UnsafePointer<Void>) -> Int32 {
let compareFunc: CompareFunc = UnsafeMutablePointer(thunk).memory
return compareFunc(l,r)
}
extension Array where Element: Comparable {
func quicksort() -> [Element] {
var array = self
var compare = Element.compare
qsort_r(&array, array.count, strideof(Element), &compare, cmp)
return array
}
mutating func quicksortInline() {
self = quicksort()
}
}
var myArray = ["Hello", "ABC", "abc", "test"]
myArray.quicksortInline()
print(myArray)
What did we do? Let’s recap. We started with a very simple invocation of qsort
and sorted some numbers. Then, we put it into a function and made it work on any array of numbers. The moment we started making it generic, things got really complicated. The blocks were relatively okay, but once we needed to use this qsort_r
strategy with the void pointer, things got a bit magical. However, this is a very common pattern in C libraries, and anytime you want to wrap a C library, you can use this technique. You might also think, “Why would I ever want to wrap a C library?” I think that once Swift is open-source, we want to run it on multiple platforms, for example Linux. And on Linux we will probably not have access to all the Apple frameworks that exist on Cocoa and iOS. We will need to wrap frameworks all of a sudden for networking, drawing things, etc. Then, it will be important that you know how to work with C libraries. That’s why I think this can be very useful.
Q&A (39:47)
Q: If you want to include C functions and C libraries in your project, you need to copy the files and they will be available to Swift inside the same modules?
Chris: Yes, it’s similar to including Objective-C from your Swift.
Q: Say you have a C library and you only have API data exposed as function pointers. You don’t have any block API or any thunk API. Are you completely out of luck, or can you pass in some context?
Chris: Yes, you’re out of luck if there are no thunks or what is often called context.
Q: What was your motivation to look into this? How did you feel when you first experienced it, and how did it make you feel in the end?
Chris: Let’s start with the motivation. Normally, I would not use C libraries, but when Apple open-sourced Swift, I started thinking it could be interesting. At the same time, I teamed up with Airspeed Velocity to write a new book on Swift. It was before the open sourcing that we decided write a book. Then, Apple open-sourced Swift and I realized, “My God, we’re going to have Swift everywhere.” We will not have Apple’s frameworks available on most of these platforms. I would love to write Swift on Linux and write my backends in Swift, but, I’ll need to learn how to wrap C APIs. Therefore, in our book, we decided to double down on interfacing with C.
If you work with C APIs that are asynchronous, you need to think a lot harder about memory management. You need to retain things and make sure they don’t go out of memory when you’re out of scope. If you read the C book, the Kernighan and Ritchie one, it’s very simple. But, if you work with C APIs, there are so many differences between libraries. It can be super intimidating. And it is intimidating for me. Then, as I realized there are only a few techniques you need to master to wrap these APIs and implement them in Swift, I felt really powerful. Now I know that I can work with almost any C library and use it in Swift. I write the wrapper once, I test it thoroughly, and then I have all this functionality.
Q: A nice project showed up in GitHub called SwiftGo that bridges Go and Swift and it seems promising. What’s your opinion about checking what else is available to wrap Swift around it, like Go?
Chris: I don’t know exactly what the SwiftGo people did, but if I recall correctly, they wrapped the library that powers Go, not Go itself. I think this is very powerful because now you can use it. I’m happy to see more people wrapping C libraries in Swift.
Receive news and updates from Realm straight to your inbox