Swift is the main programming language for Apple platforms. This article explores some ways of using this language from the command line.
The information here is intended for those who are familiar with command line development, but who may not know anything about Swift. We will use a trivial two-line Swift program as an example.
This material comes from the author’s experience developing
the qp
application. These procedures work, or at least seem
to work, but I cannot explain why they work, since I do not know
the inner workings of this environment.
Instead, this article is from the perspective of someone who was familiar with command line development, but who knew nothing about Swift. It summarizes the things I learned along the way, so that others following the same path can have a faster start.
The examples below assume that you are working inside the Terminal app on macOS. You can follow along by entering any fixed-width text shown in bold.
Interactive Swift
The most direct way to use Swift from the command line is to run the
interpreter interactively. The name of the Swift interpreter is
simply swift
.
You may be asked to grant permission to a debugger process when you run
the Swift interpreter this way. You can manually control the permissions
for developer tools using DevToolsSecurity(8)
.
swift Welcome to Apple Swift version 5.3.1 (swiftlang-1200.0.41 clang-1200.0.32.8). Type :help for assistance.
When you run the interpreter, you get a “Read Eval Print Loop,” or REPL. You type code directly to the interpreter, and it is executed immediately. For example:
1> print("Hello") Hello 2> print("Goodbye") Goodbye 3> ^D
To quit the interpreter, type Control-D
, shown
as ^D
above.
This two-line program will be all the Swift code used for this article.
Swift as a Scripting Language
The Swift interpreter can also be used to run scripts. You write a file
whose first line names swift
as the interpreter to use for
running the rest of the file.
Save the following text into a file called greeting.swift
:
#!/usr/bin/swift print("Hello") print("Goodbye")
The first line of a Swift script begins
with #!
, followed by the name of the
interpreter. This is the same as for any other Unix script.
After creating the above file, you can make it executable and then run it directly:
chmod +x greeting.swift ./greeting.swift Hello Goodbye
In my opinion, this is the second best way to experiment with Swift while learning the language. It provides fairly quick response and immediate feedback. (The best way is described later in this article.)
Compiled Swift
After writing a Swift script, you can compile it to a binary executable
using the Swift compiler, which is
named swiftc
.
The swiftc
compiler allows the -o
option to
specify an output file name.
swiftc -o greeting greeting.swift ./greeting Hello Goodbye
Note that you do not need to remove the first line in the script file
that begins with #!
. The Swift compiler ignores it.
Multiple Source Files
For any non-trivial program, you will need to divide your code into a number of source files.
This is where Swift gets a little tricky. The complication has to do with:
- A Swift program’s entry point, and
- Top-level (global) versus non-top-level code
Entry point
If you write C code, you know that a program’s entry point is a function
named main
.
For Swift, the entry point is a file
named main.swift
.
Every program you wish to generate from more than one Swift source file
must have a single source file named main.swift
. Its
contents are equivalent to the body of the main
function in
a C program.
To illustrate this using our example, we will now split our two-line program into three source files. (In industry jargon, this process is called “Growth-Oriented Refactoring,” or GORE.)
First, create a file hello.swift
to have the following
content, which is a single function definition.
func hello() { print("Hello") }
Next, create a file goodbye.swift
to have the following
content, which is again a single function definition.
func goodbye() { print("Goodbye") }
Finally, create a file named main.swift
to have the
following content. It contains two function calls.
hello() goodbye()
To produce a binary from these three Swift files, pass all the files
to swiftc
in a single invocation:
swiftc -o greeting hello.swift goodbye.swift main.swift ./greeting Hello Goodbye
Top-level versus non-top-level code
The file named main.swift
is the only file that can contain
top-level code, meaning code not inside a containing scope.
In our example, the files hello.swift
and goodbye.swift
each contain a function definition. The
actual code they contain is within that function’s scope, and therefore
is not top-level code.
On the other hand, the file main.swift
does contain
top-level code. Its two function calls are not enclosed in a containing
scope.
Top-level code is only allowed in a file named main.swift
.
Building with make
Because you can run the Swift compiler on the command line, you can
automate your builds using make
.
For our example, we can write a simple Makefile
as follows:
greeting: hello.swift goodbye.swift main.swift swiftc -o $@ $^
(Note that the second line must be indented with a
single TAB
character, not spaces.)
This causes the compiler to re-run whenever any of the three Swift files changes, generating a new version of the program.
Running with make
If you use make
to build your program, you might as well
also use it to run your program. Add another rule to
your Makefile
after the previous one, as follows:
run: greeting ./greeting
You can then run your program by typing:
make run
For me, this is the best way to learn and experiment with Swift (or anything else that can be reduced to these steps):
- Edit
make
make
run
I dare say there is no other development environment that can even hope to approach the speed at which one can experiment with ideas as these three simple steps. (If you can think of one, I can retort that even step 2 above is redundant.)
Debugging
Debugging code from the command line can be quite cumbersome.
The swiftc
compiler takes the same -g
option as
most other command line compilers to generate a debuggable executable:
swiftc -g -o greeting hello.swift goodbye.swift main.swift
You can then debug the resulting file using the lldb
debugger:
lldb greeting (lldb) target create "greeting" Current executable set to 'greeting' (x86_64). (lldb)
In order to debug this way, you obviously need to know how to
use lldb
through its command line interface.
I personally have no problem with debugging by typing commands
to lldb
. What I do have a problem with is the way command
line debuggers show the current line after hitting a breakpoint. They do
not show the source code when debugging as when editing. Instead, they
print the current line and its neighboring lines on every stoppage:
(lldb) br set -n hello Breakpoint 1: where = greeting`greeting.hello() -> () + 8 at hello.swift:2:9, address = 0x0000000100000988 (lldb) run Process 12345 launched: ... Process 12345 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 frame #0: 0x0000000100000988 greeting`hello() at hello.swift:2:9 1 func hello() { -> 2 print("Hello") 3 } Target 0: (greeting) stopped. (lldb)
For this reason, I would not use such an interface if a better alternative is available. However, if you really had no other choice, it will get the job done. That is, it will allow you to debug your program.
I can suggest two alternatives, which I personally switch between, depending on whether the program uses Apple-specific APIs.
Debugging alternative 1: Xcode
If you prefer command line development, learning a complicated graphical IDE is an unappealing idea, but you may have no choice if you want to debug your program.
What you can do is write your Swift program using the methods described in this article, and then import your files into Xcode just to do the debugging.
There are many resources on learning Xcode. You can start with the official Apple documentation.
Debugging alternative 2: Emacs and realgud
This is an alternative only for developers who use the Emacs editor.
The Emacs editor, combined with its
GUD
library, allows seeing the source code when debugging in the same way
as when editing. However, with Swift, this setup does not work, because
GUD does not support lldb
.
An alternative that does work is
realgud, along with its
interface
to lldb
,
realgud-lldb. If
you are an Emacs user, you can look into these packages. I can report
that this combination will work with Swift.
External Links
For more regarding main.swift
, see the following article.
Pay attention to the section titled “Global Variables,” which is not
covered above.
https://developer.apple.com/swift/blog/?id=7
For more on the Swift REPL, see the following article.
https://developer.apple.com/swift/blog/?id=18
If you use make
run
, you may want to pass
command line arguments to the underlying program. The following link
describes how to write your Makefile
to allow this.
https://stackoverflow.com/questions/2214575/passing-arguments-to-make-run