Swift for Web Devs - Part 1

Friday, October 4th, 2024 #dev

Swift for Web Devs - Part 1

This is the first post in a series about Swift and SwiftUI for web developers, aimed at highlighting the beautiful aspects of the language. To learn more about Swift, refer to the official documentation.


Jumping from web development into lower-level languages like Swift can seem a bit intimidating. But Swift, especially Swift 6 along with SwiftUI, turns out to be surprisingly approachable and useful for front-end developers — and it’s not all about crafting applications for Apple devices. Swift can sometimes be a good choice even in scenarios where one might typically consider Rust for its performance advantages.

In this first post, we’re diving into why Swift might just be the secret weapon for web developers looking to level up their coding game. I’ll share insights into the aspects of Swift that stood out to me initially. We’ll also explore its similarities to TypeScript, which makes it particularly easy to learn for web developers.

Compatibility and Uses

Before we begin, let’s address the elephant in the room: yes, Swift is developed by Apple, and a significant usage of Swift is to write native Apple apps with SwiftUI. However, Swift is a perfectly valid solution for writing any application code, from web servers and command-line tools to code for embedded devices. It is open-source, has support in IDEs like VSCode, IntelliJ, Neovim, etc., and can be run and built on macOS, Windows, and Linux.

Swift looks a lot like TypeScript

If you’re accustomed to writing TypeScript, you’ll quickly feel at home with Swift.

Here’s an example of defining a variable in Swift:

let name: String = "Pausly"

Notice how the type is added after a colon (:).

Semicolons (;) at the end of a statement are optional in both languages and usually aren’t added.

Like TypeScript, Swift boasts an impressive type system capable of inferring types in many cases, making code easier to write and read:

// Exactly the same as the previous example.
// The type of `name` is inferred to be `String`.
let name = "Pausly"

Swift utilizes let and var for declaring constants and variables, respectively, contrasting with TypeScript’s const and let:

  • var is used for variables that can change.
  • let is for constants that are immutable.

Support for string interpolation exists in Swift, albeit with slightly different syntax:

let name = "Pausly"
let greeting = "Hello \(name)"

Function definitions also bear similarity with minor syntax differences:

func greeting(name: String) -> String {
    return "Hello \(name)"
}

A convenient feature is that functions containing only a single expression implicitly return that expression, simplifying the previous example to:

func greeting(name: String) -> String { "Hello \(name)" }

In Part 2 of this series, I’ll delve into Swift Closures, highlighting the elegant methods for defining closures in Swift.

Arrays and other collections in Swift are straightforward, with slight syntax variations from TypeScript. For example, an array of strings in TypeScript string[] corresponds with [String] in Swift. Coming from TypeScript, the Swift syntax looks like the tuple syntax, but Swift has it’s own tuple type and doesn’t need to abuse the array for that functionality.

func greeting(names: [String]) -> String {
    "Hello \(names.joined(separator: ", "))"
}
// Tuples:
func getCoordinates() -> (Int, Int) {
    (10, 20)
}

Swift’s APIs are thoughtfully designed, using the passive voice for methods that return modified versions of objects rather than modifying the objects themselves, just like the .joined() method we used above.

Control flow

Control structures such as If, Switch, and For share a strong resemblance with TypeScript. Here are a few examples that don’t need much explanation:

// Parentheses around conditions are not required
if age < 18 {
    throw BounceError.tooYoung(requiredAge: 18)
}

let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names {
    print("Hello, \(name)!")
}

// Swift doesn’t require the `break` keyword for switch cases.
// If you need the usual behaviour though, just use the
// `fallthrough` keyword.
switch someCharacter {
case "a":
    print("The first letter of the Latin alphabet")
case "z":
    print("The last letter of the Latin alphabet")
default:
    print("Some other character")
}

Asynchronous Programming

While the examples provided thus far have been relatively straightforward, Swift’s syntax remains familiar even when delving into more complex concepts, such as asynchronous functions:

func getNames() async -> [String] {
    let users = await listUsers()
    return users.map { $0.name }
}
  • Functions are declared as async using the async keyword.
  • In Swift you don’t need to wrap the return type in as you do in TypeScript Promise<string[]>.
  • The await keyword pauses execution until the asynchronous task is complete, akin to TypeScript.
  • The last line shows Swift’s closure syntax. We’ll learn more about in the next post.

Conclusion

This overview briefly introduces Swift’s basic features, showcasing its similarity to TypeScript and the familiar, comfortable feeling it offers. Of course, there’s much more to Swift, which I’ll explore in subsequent parts of this series.

Continue with part 2 of this series.

If you enjoyed this post, subscribe to this blog in the RSS reader of your choice.