Photo by RichardHorvath on Unsplash

Hi guys ☕️☕️! So I’ve decided to pick up native app development after all these years and I’m excited to have chosen iOS and SwiftUI as my platform and tooling. I’m not entirely new to native app dev, as I’ve had past experiences with the Android platform back in the Java + XML days. I can’t say I enjoyed the experience then, so, now I’ve come full circle with myself again and I’ve found a platform and tooling (language included) I know (not think) I will enjoy.

I picked up a 100-day course by Paul Hudson on Hacking With Swift, and I can’t lie, I’ve been out-pacing the course, to tell you how much I’ve been enjoying it, or not.

I’ve familiarized myself with most of the language syntax and patterns as it seems to have quite some unconventional syntax and some language designs that I think were thought out of the box. I wouldn’t also fail to mention that I see a lot of similarities between Swift and Rust; now I can’t tell which of both languages pioneered the ideas common to them. Anyways, I learned Rust first, so I’m just going to ignorantly believe Rust pioneered these ideas.

I’ve always wanted to do iOS development, and I don’t know what exactly about the platform has been enticing me. So I started taking a look at Swift in years past but with no serious commitments. Fast forward to 2024: I wanted to start writing servers in Ruby, so I started reading some docs. So so many things I want to do, but Darwinism always takes charge, not scale of preference or opportunity cost, now, ‘cause it’s mostly out of my control; more like natural selection—nature’s own choosing. I see the hand of God in these things, ha! Long story short, I lost track of the—oh, and it was Ruby on Rails, I believed you already knew that—I lost track of the Ruby on Rails doc I was studying and just last week out of boredom and idleness I fired up my XCode, found the Hacking With Swift course, created a playground and it’s been working for me so far. I guess Ruby on Rails would come another time. Even better nothing stops me from learning both simultaneously. I could be writing a simple iOS application and writing a server for it in Rails. After all, I learn best by doing rather than spending time reading docs only.

I believe the reason I’ve been out-pacing the course schedule is that I’ve been learning variables, constants, control flow, functions, classes, etc. I find these parts so boring because the idea is the same across programming languages and Swift ain’t my first language. I’m so eager to get to the interesting parts. When do I get to build a UI, anyone?

Here are some of the similarities I’ve seen between Swift and Rust:

Immutability

First, just as you see in Rust, immutability is a first-hand concept in Swift also. Values created with the let keyword are immutable, and that could mean a lot of things: variables declared with let cannot be re-assigned and for as long as they hold a value type, rather than a reference type, mutating operations cannot be performed, even a method that causes mutation to the value they hold cannot be called. They’d have to be declared with a var to call a mutating function or perform mutating operations. For this reason, Swift has a keyword, mutating, for marking methods that mutate state, just as Rust has a mut keyword.

struct Node {
    let symbol: String
    var code: Int
}

We have just defined a struct (There are structs in Rust too, but that may not count, as C has always had struct). This struct declares a constant variable, symbol, and a…variable variable, code, 🥴. Now watch what happens when we create some Node structures and assign them to a let and var variable each.

let node1 = Node(symbol: "sym", code: 1)
var node2 = Node(symbol: "bol", code: 2)

node1.code = 2node2.code = 1

Line 4 would cause a compile error because although the field in the structure we tried to change is declared with var, that is, it is mutable; the variable node1 was declared as immutable. That’s dicey because we are not changing node1 we are only changing code inside node1. But structs are value types, not reference types, which means anytime you move them around or you write to them, a copy is made. This copy has to be reassigned to our original node1 variable but that would be impossible because it was declared with let marking it as immutable, which prevents it from being reassigned. This generally means that to allow changing a mutable state Node.code in our struct we have to assign the value to a mutable variable declared with var. That is why line 5 would work just fine.

You might wanna take a look at this article that explains Copy-on-Write (CoW) in Swift

Enums and Pattern Matching

Just like I’ve seen in Rust, enumerations in Swift are super-packed and surpass my regular idea of enums. For example in TypeScript and/or Python we get a Union type using the pipe (|) operator or the Union[Int, None] type for example in some versions of Python. You know what? Swift doesn’t have this but you could achieve the same functionality using an enum, who’d have thought? I’ve only ever seen enums packed with this much power in Rust. So what we can do is create some type of “type monad”

enum Either<This, That> {
    case this(This)
    case that(That)
}

The power of enum in Swift becomes more evident when you combine it with Pattern Matching

func findUser(using idOrEmail: Either<Int, String>) {
    switch idOrEmail {
    case .this(let id):
        print("Find user with ID \(id)")
    case .that(let email):
        print("Find user with email address \(email)")
    }
}

We can call our findUser function giving it an ID (integer) or an email address (string) wrapped in a Either type, that’s basically an enum.

// First call using the ID
findUser(using: Either.this(1))

// Second call using the email address
findUser(using: Either.that("caleb@example.com"))

The first time I saw this style of programming was when I was learning Rust and I enjoyed these ideas. If you’ve ever come across Rust then you must at least know of Option, Some, and None, similar also to Swift’s Optional. Same way you could pattern-match those types in Rust, so also in Swift. Enum in Swift is so good it’s how you define errors in your application.

Protocols and Traits

The foundational programming construct commonly called “interface” is termed “protocol” and “trait” in Swift and Rust, respectively. Like in Rust, interfaces or traits or protocols can be adopted incrementally, and there’s more to them than meets the eye. For example, you may define all your core functionalities in Rust and for each of the traits you need your struct to conform to you implement them separately at a later time. So also in Swift, there are extensions and you could have defined your type and later adopt certain protocols using extensions.

class Node {
    let symbol: String
    let weight: Int
    let left: Node?
    let right: Node?
    var code: Int

    init(symbol: String, weight: Int, left: Node? = nil, right: Node? = nil) {
        self.weight = freq
        self.symbol = symbol
        self.left = left
        self.right = right
        self.code = -1
    }
}

We have just defined a Node struct, and now that we think about it we want it to conform to the Comparable protocol so we can compare two nodes. So we can go ahead and use extensions just as you would the impl [Trait] for [Struct] { ... } in Rust.

extension Node: Comparable {
    static func == (lhs: Node, rhs: Node) -> Bool {
        return lhs.weight == rhs.weight
    }

    static func < (lhs: Node, rhs: Node) -> Bool {
        return lhs.weight < rhs.weight
    }
}

For me this is nice; and if nothing at all, it’s a practical example of one of SOLID principles—Open-Closed Principle—which is the “Open for Extension; Closed for Modification”. We can see that we don’t have to reach into the source to modify the functionalities of the Node struct before we can build upon its functionalities. Take for example one of Swift’s standard library is short of a feature we think would be great to have. We don’t have to go look for the source code of this standard library and modify it just to add our desired feature to it, we could just extend it from anywhere and Bob’s our cousin :)

Bonus: Notice I’ve defined Node using class and not struct? This is because the type holds a reference to itself in a recursive manner and structs are value types rather than reference types; their size has to be known statically, unlike reference types whose sizes can be dynamically evaluated. A similar thing occurs in Rust, where you have to wrap the type holding a reference to itself in a Box since Rust doesn’t even have classes

pub struct Node {
    symbol: String
    weight: i32
    left: Option<Box<Node>>,
    right: Option<Box<Node>>,
    code: i32,
}

You may check out this SO answer regarding Swift

And that wraps some of the similarities between Rust and Swift I’ve seen over the one-week course of learning Swift. You can tweet to me @relaongman if there are any more you find interesting.

A more practical use of Swift

To help consolidate my knowledge of Swift so far, I gave myself a task. The task was to build an Huffman tree using Swift. I’m going to drop the result of that task here, but I won’t be explaining the algorithms because that isn’t what this is about, maybe just some language features I’ve employed to perform the task.

import Foundation

This is the first line. The “Foundation” library, I noticed, does a lot of foundational things like providing extensions to various types; an example is String. For example, the String method appending is only available when I have this import statement present. I’m going to believe the “Foundation” library defines some extensions on the String type.

Other than that is me defining my implementation for a priority queue using structs. A priority queue or a min-heap is essential for building a Huffman tree.

HuffmanTree.swift
struct PriorityQueue<T: Comparable> {
    private(set) var heap: Array<T> = []
    private let comparator: (T, T) -> Bool

    init(comparator: @escaping (T, T) -> Bool) {
        self.comparator = comparator
    }

    var size: Int {
        return heap.count
    }

    var isEmpty: Bool {
        return heap.isEmpty
    }

    func peek() -> T? {
        return heap.first
    }

    mutating func enqueue(value: T) {
        heap.append(value)
        siftup(heap.count - 1)
    }

    mutating func dequeue() -> T? {
        guard !heap.isEmpty else { return nil }

        if heap.count == 1 { return heap.removeLast() }

        let root = heap[0]
        heap[0] = heap.removeLast()
        siftdown(0)
        return root
    }

    private mutating func siftup(_ from: Int) {
        var node = from
        var parent = (node - 1) >> 1

        while node > 0 && comparator(heap[node], heap[parent]) {
            heap.swapAt(node, parent)
            node = parent
            parent = (node - 1) >> 1
        }
    }

    private mutating func siftdown(_ from: Int, to: Int? = nil) {
        var node = from
        var left = (node << 1) + 1
        var right = left + 1
        var parent = node
        let end = to ?? heap.count - 1

        while left <= end {
            if comparator(heap[left], heap[parent]) { parent = left }
            if right <= end && comparator(heap[right], heap[parent]) { parent = right }

            guard parent != node else { return }

            heap.swapAt(node, parent)
            node = parent
            left = (node << 1) + 1
            right = left + 1
        }
    }
}

We have defined our implementation for a priority queue in a struct. Using a generic T we also allow our queue to hold any value, hence generic, as long as it conforms to a protocol called Comparable. This means we don’t care whatever is being added to the queue we only care that such-ever can be compared to each other using comparison operators such as < for less than, == for equals to, > for greater than, and combinations like <= and >=.

As an initializer for our struct, it takes a closure which is used to compare any two values from our queue, hence the reason why we need the values T in our queue to be at least comparable.

Something about this closure in our init method that may seem strange is the @escaping annotation. What does it do? It lets Swift know that this closure being passed to the init function or method “escapes” the context of this method. Simply put, the closure may not be executed until after the init method has run and returned but may be stored and called from another context other than that of the method or function it was originally passed to.

You can see why it’s important to know that, right? Because if you’re coming from languages like JavaScript, Python, etc, as I am, you know you can just pass callbacks or closures around loosely.

Other than that, we’ve got getters size and isEmpty implemented to find out the size of items in our queue and whether our queue is empty.

Notice the private(set) syntax used when defining the heap field on the priority queue. This means the heap is publicly available for viewing, but mutations to it are in-house. We put the heap behind a show glass because we want whoever is passing by to see it but only people inside the house can touch it. We’ve also labeled every method that modifies the state of our structure with a mutating attribute. This lets the Swift compiler know what methods can be called when an instance of our structure is assigned to a let declaration and/or a var declaration, alluding to an explanation I made earlier when talking about immutability being a first-hand concept in Swift, like Rust.

I also like the fact that function parameters are labeled in Swift, like we could have keyword args in Python. Take this Python code for example:

# Since `*` in function params in Python marks *vargs and only **kwargs can come after
# *vargs, it makes sense that after an empty *vargs marked by "*" other params have to
# be keyword args.

def gcd(*, a: int, b: int):
    a, b = (b, a) if b > a else (a, b)
    if b == 0:
        return a
    return gcd(a=a % b, b=b)

def main():
    print(gcd(a=108, b=144))

In the above Python code you’re not allowed to call gcd without passing the parameter labels. Doing so will result in a TypeError:

>>> gcd(108,144)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: gcd() takes 0 positional arguments but 2 were given

This helps reduce ambiguity and bugs while also improving DX especially when there are parameters of the same type following each other. You do not have to always go to the function declaration to check the order in which the ambiguous parameters (by type) are to be passed. Unfortunately, JavaScript and even Rust, a more modern language, lack this. It’s very easy to lose track of which parameter is which when the parameter size begins to grow, having similar types as seen above.

Talking about parameter labels, Swift one-ups Python in that it is possible to alias parameter labels, which is very useful when passing arguments to the function. It helps the function call to read out more intuitively but at the cost of verbosity.

func brushTeeth(using material: String) {
    print("Brushing teeth using \(material)")
}

brushTeeth(using: "Peroxide")
brushTeeth(using: "Mouthwash")
brushTeeth(using: "Toothbrush")

The parameter alias usually reads out intuitively at the call site but may not necessarily make sense inside the implementation as seen above. Imagine if we had to do:

func brushTeeth(using: String) {
    print("Brushing teeth using \(using)")
}

// or to circumvent that...

func brushTeeth(material: String) {
    print("Brushing teeth using \(material)")
}

// ...but this

brushTeeth(material: "Peroxide")

Without aliasing, sometimes, the parameter name either makes sense to whoever declares the function or whoever is calling it, but not both at the same time. Not that any of it is bad, it’s just better with the alias, but at the cost of verbosity. So just like any other thing in life, it’s a trade-off.

That brings me to: ignoring parameter labels by aliasing them with an underscore _ as seen in some of our methods, like siftup in our priority queue struct

But why do arguments still gotta be positional in Swift even when using parameter labels?

🚫 Argument 'a' must precede argument 'b'

Other than all of what I’ve mentioned, I do not think anything looks too strange for anyone coming from a different programming language background such as mine—

Ah, guards!

Guards are just a way of incentivizing developers to use early return rather than an unduly complex, difficult-to-read if-else branch. Guards check the condition necessary for execution to continue and provide an else block which must either return or throw when the condition isn’t met.

Now that all of that is settled maybe we can move on with our Huffman tree and see if there are more stuff to swiftly untangle.

HuffmanTree.swift
class Node {
    let symbol: String
    let weight: Int
    var code: String
    let left: Node?
    let right: Node?

    init(symbol: String, weight: Int, left: Node? = nil, right: Node? = nil) {
        self.weight = weight
        self.symbol = symbol
        self.left = left
        self.right = right
        self.code = ""
    }
}

extension Node: Comparable {
    static func == (lhs: Node, rhs: Node) -> Bool {
        return lhs.weight == rhs.weight
    }

    static func < (lhs: Node, rhs: Node) -> Bool {
        return lhs.weight < rhs.weight
    }
}

Nothing to see here, just implementing comparison operators for our node for conformance’s sake.

HuffmanTree.swift
func characterFrequency(_ text: String) -> [Character: Int] {
    var dict = Dictionary<Character, Int>()

    for i in text.indices {
        let char = text[i]
        dict[char, default: 0] += 1    }

    return dict
}

Nothing to see here, also, other than dictionaries having a way to specify default value when accessed, in case the key does not exist or is undefined. Something TC39 never thought of. Just saying…don’t come at me. When was Map added to JavaScript? Yet I have to do:

function doSomethingWithMap(map: Map<string, number>) {
  map.get('key') ?? 0
  // not `map.get("key", 0)`
}

Back to Huffman tree:

HuffmanTree.swift
func huffmanTree(_ charFreq: [Character: Int]) -> Node {
    var q = PriorityQueue<Node> { $0 < $1 }    charFreq.forEach({ q.enqueue(value: Node(symbol: String($0), weight: $1)) })
    while q.size > 1 {
        let left = q.dequeue()!
        let right = q.dequeue()!

        let node = Node(
            symbol: left.symbol.appending(right.symbol),
            weight: left.weight + right.weight,
            left: left,
            right: right
        )

        q.enqueue(value: node)
    }
    return q.heap[0]
}

I love this syntax of passing closures! Very simple! $0 and $1 are positional parameters for the signature (T, T) -> Bool in the init function of the priority queue struct; you don’t even have to open a parenthesis. You can see a similar closure with positional parameters in the following line, in the forEach method call. We didn’t have to open a parenthesis, but I did so in contrast to the previous line. This adjusts our priority queue to treat lower values as higher priority.

HuffmanTree.swift
func traverse(node: Node, val: String = "", collect: (Node) -> Void) -> Void {
    node.left.map { traverse(node: $0, val: "\(val)0", collect: collect) }    node.right.map { traverse(node: $0, val: "\(val)1", collect: collect) }
    guard node.left == nil && node.right == nil else { return }
    node.code = val

    return collect(node)
}

func main() {
    let text = "The quick brown fox jumps over the lazy dog. The quick blue cat sits by the window."
    let charFreq = characterFrequency(text)
    let tree = huffmanTree(charFreq)
    var collection = Array<(String, String)>()

    traverse(node: tree) { node in
        print(node)        collection.append((symbol: node.symbol, code: node.code))
    }

    // Do whatever you like with collection, compression, whatever...
}

main()

Both node.left and node.right are Optional. .map is a way of unwrapping optionals as you would unwrap in Rust. If the optional contains a value, the value is passed to the closure passed to the mapping function, otherwise, the mapping function is not called. I think this is clean!

One last thing: instances of classes in Swift unlike structs aren’t very informational when printed. One thing is to implement the CustomStringConvertible to print a human-readable format of a class instance. Rust has an equivalent called Debug where you could just annotate types with #[derive(Debug)] or implement it yourself if necessary.

I would show the code example here, but it’s not my code so I’ll just provide the link to the implementation I saw on another blogpost. It’s eight years ago as of the time of writing and I do not know if there’s a better way to do this now, I’m relatively new to Swift, but it works.

That’s it guys. It’s a wrap! Maybe I should have titled this “A Fool’s Perspective of Swift in Contrast—Pros and Cons”.

Quod scripsi, scripsi.