Let's Build A 'cat' In Swift 2

As a homework in one of the early college classes, I was asked to write unix commands such as cat in C. Let's do that in Swift today! To make things interesting, let's pretend we are on Linux. That means no Xcode nor Foundation can be used.

It's hard to find a simpler unix program than cat: It takes a list of file names from the shell and write the content of each file to stdout. When no argument is given, it uses stdin as the source of its output.

Writing it in C is trivial. Swift has exellent support for leveraging C. But to call even the standard C functions, we need to import them first.

The swiftc command can compile a pure Swift source file like this:

swiftc cat.swift -o cat

We can add Objective-C bridging headers with the argument -import-objc-header. But to import the standard C functions, we also need to specify path to an SDK:

swiftc -sdk $(xcrun --show-sdk-path --sdk macosx)\
       -import-objc-header bridge.h\
       cat.swift\
       -o cat

Instead of typing/copying that command, save this Makefile to the same directory as cat.swift:

SDKPATH = $(shell xcrun --show-sdk-path --sdk macosx)
CBRIDGEHEADER = bridge.h
TARGETS := cat

.PHONY : all $(TARGETS)

all: $(TARGETS)

$(TARGETS):
    swiftc -sdk $(SDKPATH) [email protected] -import-objc-header $(CBRIDGEHEADER) -o $@

Now make cat should take care of the compilation.

Since file I/O is the only concern, we'll need C APIs from stdio.h, so bridge.h is a one liner:

#import <stdio.h>

The standard C function for opening a file is fopen:

FILE * fopen ( const char *filename, const char *mode );

Hmmmm, how do we deal with all those pesky '*'s?

To reference a certain C Type in Swift, we can use UnsafePointer<Type> or UnsafeMutablePointer<Type>. To make our lives easier, Swift Strings automatically bridge to const char *. In other words, we can treat the signature of fopen as if it's the following:

func fopen( filename: String, mode: String ) -> UnsafeMutablePointer<FILE>

A character in C is represented by a byte in memory. Therefore Swift sees a char as of type Int8 (8-bit integer). So a char * would be referenced as UnsafeMutablePointer<Int8> in Swift. So getline, a function from POSIX

ssize_t getline( char **lineptr, size_t *n, FILE *stream );

would look like this in Swift:

func getline(
    inout lineptr: UnsafeMutablePointer<Int8>,
    inout n: UInt,
    stream: UnsafeMutablePointer<FILE>
) -> Int

It returns the number if characters it finds.

We now can open a file, read and print its content line by line, and close it with:

func fclose(stream: UnsafeMutablePointer<FILE>)

Repeat this on each file specified in Process.arguments, or simply read from stdin, and we have a cat! Here's a screenshot of it displaying its own code:

Swift cat

The code is also available in this gist.