Using Linux C APIs in Swift: Glob
With Apple's announcement that Swift is coming to Linux, it's time to think about how Swift can be leveraged as a system language rather than simply an app language.
In order to function as a systems language, we need an easy interface to the primitives on a system. On the BSD-like OS X, that means using the myriad BSD functions found in /usr/share/man/man3
(well, the lower-case ones mostly). In this post, I'll show how to wrap glob(3) with Swift, since I needed it for a recent project and it's a reasonably simple introduction to Swift-C interaction.
The glob API
On OS X, you can access the manpage via $ man 3 glob
. The manpage shows three different functions you can call, though one of them (glob_b
) uses a block rather than a function pointer, so I'm not sure whether that's going to make the port over to Linux. In any case, let's focus only on glob
and globfree
.
The glob function
The glob
function, as the manpage indicates, has 4 arguments and an error-indicating return value. The arguments are, in order: the pattern, some behavior flags, an error function pointer, and the pass-by-reference result object. The return value is non-zero for errors, so our implementation will just pretend it didn't find any results upon error.
Let's begin by setting up our Glob class:
class Glob: CollectionType {
var globFlags = GLOB_TILDE | GLOB_BRACE | GLOB_MARK
init(pattern: String) {
var globObj = glob_t()
glob(
pattern.cStringUsingEncoding(NSUTF8StringEncoding)!,
globFlags,
nil,
&globObj
)
// store results somewhere
globfree(&globObj)
}
}
This doesn't quite compile, because we need to adhere to the CollectionType
protocol. We can see, though, that we've successfully wrapped glob
and globfree
in Swift. We'll take care of that gross exclamation point in the string decoding and the storing of results in a minute, but first, why are we adhering to the CollectionType
protocol?
The CollectionType
Protocol
There are a number of enumerable protocols in Swift, including SequenceType
and CollectionType
. In our case, it makes more sense to say we want a "collection" of paths rather than a "sequence" of paths. To me, a sequence means that once we've received an element in a sequence, we'll never need it again. I can anticipate use cases where that might not be true, so that's why I'm using CollectionType
. Why do this? Once we adhere to either SequenceType
or CollectionType
, we get for...in
functionality for free. Think of it as NSFastEnumeration
for Swift.
To adhere to the CollectionType
protocol, we need to provide two properties—startIndex
and endIndex
—and a subscript
. We can do that like this:
class Glob: CollectionType {
// non-CollectionType code elided...
var paths = [String]()
var startIndex: Int {
return paths.startIndex
}
var endIndex: Int {
return paths.endIndex
}
subscript(i: Int) -> String {
return paths[i]
}
}
Adding a Little Safety
Since we're interacting with a C API, we're faced with things like char arrays, which aren't exactly String
s, since they don't have an encoding. With this as a potential (though extremely unlikely) scenario, we want to extract the data from our glob_t
object, sanitize it, and store it in a [String]
. We also want to guard against weird input so we can get rid of the exclamation point in our initializer:
class Glob: CollectionType {
// CollectionType declarations elided
private var globFlags = GLOB_TILDE | GLOB_BRACE | GLOB_MARK
var paths = [String]()
init (pattern: String) {
var globObj = glob_t()
if let cPattern = pattern.cStringUsingEncoding(NSUTF8StringEncoding) {
if executeGlob(cPatt, globPtr: &globObj) {
populatePaths(globObj)
}
}
globfree(&globObj)
}
private
func executeGlob(cPattern: [CChar],
globPtr: UnsafeMutablePointer<glob_t>) -> Bool {
return 0 == glob(cPattern, globFlags, nil, globPtr)
}
private
func populatePaths(gt: glob_t) {
for var i = 0; i < Int(gt.gl_matchc); i++ {
if let path = String.fromCString(gt.gl_pathv[i]) {
paths.append(path)
}
}
}
}
Conclusion
We now have an approachable Swift API for glob:
for path in Glob("~/dev/{ruby,js,swift}") {
println(path)
}
The full source code, with tests, is available as a Gist.
With the right techniques, you can abstract away C APIs in your Swift code, making your entire codebase safer and more idiomatic. It's true that glob
is a fairly simple API, but the primitives afforded us by the Swift language and the Foundation framework let us design some awfully clean abstractions.