Lev Walkin
3 min readJan 2, 2020

Swift as a C language REPL

Systems programmers who frequently switch between operating systems suffer from POSIX amnesia. That’s when you know what POSIX function to use, but forget some edge cases, return codes, or boundary conditions. What are the default F_GETFL status flags on a file descriptor opened with a O_NONBLOCK? What would fcntl(fd, F_SETSIZE) do on “/dev/null” on macOS?

Turns out, assuming you have Xcode installed, the swift CLI doubles as a fun C REPL.

Run the swift CLI from the terminal. In order for Swift REPL to pick up many of the standard POSIX functions you also have to import Foundation.

Now, we can run C functions directly. Here’s open(2):

Invoking open("/dev/null", ...) comes back with a 3, which is the expected return value. The return value (in green) is prefixed with a return value’s type, Int32. The Swift’s Int32 (also aliased as CInt) corresponds to C’s int type, which is predictably 32-bit on LP64 platforms. The Swift’s Int type, after all, can be expected to be 64-bit, so Int is different from CInt.

Let’s use Swift’s let to capture the return value:

Now, let’s do something interesting. Let’s recover the file‘s path by its file descriptor. Unfortunately, this is going to be a little involving and Swift-heavy. Here’s what to cut-n-paste as you follow along:

import Foundation
let fd = open("/dev/null", O_RDONLY)
var buf = [UInt8](repeating: 0, count: .init(PATH_MAX))
buf.withUnsafeMutableBytes { ptr in
fcntl(fd, F_GETPATH, ptr.baseAddress!)
}
String(cString: buf)

After entering the last line (String(cString: buf)) we get back the representation of the Swift’s String value containing the path of the file:

Well, I think I might have just discouraged you from using Swift as a C REPL with all this withUnsafeMadness complexity. Let’s try something simpler that does not require any buffer management. Let’s try to resize the /dev/null file and see how it fails:

As you see, this looks much more manageable, and we even recovered a printable error message corresponding to the fcntl failure.

As a final example, let’s see how to get the size of the dirent structure using Swift. The MemoryLayout can be used as C’s sizeof substitute:

Well, this is it. The Swift REPL can be used to try out standard C functions, check out values of POSIX constants (and some #defines) and sizes of structures. It can be handy or somewhat frustrating to invoke certain things, and at times you may need to know more Swift than you wanted to. But it works and it makes my day a little bit easier, on both macOS and Linux.