This year, I decided to use Advent of Code to learn the language Swift. Since there were only 12 days of tasks for 2025, here is my summary of experiences. Also check out my solutions.
Tooling
I used Swift 6.2 on Void Linux, which I compiled from scratch since there were no prebuilt binaries that worked with a Python 3.13 system (needed for lldb). It’s possible to bootstrap Swift from just a clang++ toolchain, so this wasn’t too tedious, but it still required looking up Gentoo ebuilds how to pass configuration properly. As an end user, this should not worry you too much.
Tooling in general is pretty nice: there’s an interpreter and you
can run simple “scripts” directly using swift foo.swift. Startup
time is short, so this is great for quick experiments. There’s also a
REPL, but I didn’t try it yet. One flaw of the interpreter (but
possibly related to my setup) is that there were no useful backtraces
when something crashed. In this case, I compiled a binary and used
the included lldb, which has good support for Swift.
There’s also a swift-format tool included to format source code.
It uses 2 spaces by default, but most code in the wild uses 4 spaces
curiously. I’m not sure when that changed.
Since I only write simple programs using a single source file,
I didn’t bother looking at swift-build yet.
By default, programs are linked dynamically against the standard library and are thus super compact. Unfortunately, many modern languages today don’t support this properly. (Statically linking the standard library costs roughly 10MB, which is fair too.)
The language
In general, the language feels modern, comfy, and is easy to pick up. However, I found some traps as well.
The syntax is inspired by the C family and less symbol-heavy than Rust’s. There’s a block syntax akin to Ruby for passing closures.
Error handling can be done using checked exceptions, but there are also Optional types and Result types like in Rust, and syntactic shortcuts to make them convenient.
The standard library has many practical functions, e.g. there’s a
function Character.wholeNumberValue that works for any Unicode digit
symbol.
There’s a Sequence abstraction over arrays etc. which has many useful functions
(e.g. split(whereSeparator:), which many other standard libraries lack).
The standard library is documented well.
The string processing is powerful, but inconvenient when you want to do things like indexing by offsets or ranges, due to Unicode semantics. (This is probably a good thing in general.) I switched to using arrays of code-points for problems that required this.
On Day 2, I tried using regular
expressions, but I found serious performance issues: first I used a
Regexp literal (#/.../#) in a loop, which actually resulted in
creating a new Regexp instance on each iteration; second, Regexp
matching itself is quite slow. Before I extracted the Regexp into a
constant, the program was 100x as slow as Ruby(!), and after it still
was 3x as slow. I then rewrote the solution to not use Regexps.
Prefix (and suffix) operators need to “stick” to their expression, so
you can’t write if ! condition. This is certainly a choice: you can
define custom prefix and suffix operators and parsing them
non-ambiguously is easier, but it’s probably not a thing I would have
done.
Swift functions often use parameter names (probably for compatibility with Objective-C). They certainly help readability of the code, but I think I prefer OCaml’s labeled arguments, which can be reordered and permit currying.
The language uses value semantics for collections and then optimizes
them using copy-on-write and or by detecting inout parameters (which
are updated in-place). This is quite convenient when writing code
(e.g day 4)
Garbage collection is done using reference counting. However, some
AoC tasks turned out to make heavy use of the garbage collector, where
I’d have expected the compiler to use a callstack or something for
intermediate values. Substrings are optimized by a custom type
Substring, if you want to write a function to operate on either
strings or substrings, you need to spell this out:
func parse<T>(_ str: T) -> ... where T: StringProtocol
There’s a library swift-algorithms adding even more sequence and collection algorithms, which I decided not to use.
Downsides
The compiler is reasonably fast for an LLVM-based compiler. However, when you manage to create a type checking error, error reporting is extremely slow, probably because it tries to find any variant that could possibly work still. Often, type checking errors are also confusing.
(Error messages unrelated to type checking are good and often really
helpful, e.g. if you accidentally use ''-quotes for strings
or try to use [] as an empty map, it tells you how to do it right.)
Ranges can be inclusive ... or right-exclusive ..<. Constructing
a range where the upper boundary is smaller than the lower boundary
results in a fatal error, whereas in other languages it’s just an
empty range.
Some “obvious” things seem to be missing, e.g. tuples of Hashable
values are not Hashable currently (this feature was removed in 2020,
after trying to implement the
proposal
that introduced it, and no one bothered to fix it yet?), which is
pretty inconvenient.
Likewise, the language has pattern matching for algebraic data types and tuples, but unfortunately not for arrays/sequences, which is inconvenient at times.
Since I was just picking up Swift, I had to search stuff online a lot and read Stack Overflow. I noticed I found many answers for prior versions of Swift that changed in the mean time (even for basic tasks). For a language that’s been around for over 10 years, this seems like quite some churn. I hope the language manages to stabilize better and doesn’t just get new features bolted on continuously.
In general, using Swift was fun and straight-forward for these programming tasks. For writing serious applications on non-MacOS systems, there’s also the question of library availability. Some parts of the language still feel unfinished or unpolished, in spite of being around for quite some time.
NP: Adrianne Lenker—Promise is a Pendulum