WWDC Quick Look 💓 By SwiftGGTeam
A Swift Tour: Explore Swift’s features and design

A Swift Tour: Explore Swift’s features and design

Watch original video

Highlight

This is a Swift language tour for all developers—not a simple syntax feature list. Presenter Allan Shortlidge, from the Swift compiler team, builds a social network graph data model to show how Swift’s core design principles play out in real code.

Core Content

The most common confusion when writing Swift: when to use struct vs class? How to choose between Optional and throw? How to achieve concurrency safety? Answers are scattered across documentation—beginners struggle to form a systematic understanding.

This session’s approach is direct: one social network project runs through the entire talk. Starting from “how to represent a User,” the presenter builds a complete graph data model, HTTP server, and command-line client. Every language feature maps to a real need—value semantics for data isolation, guard statements to intercept invalid input, actors to protect shared state.

Swift’s design philosophy becomes clear along this narrative: value types as the default, the compiler forcing you to handle errors and nil, the type system eliminating data races at compile time—these principles reinforce each other as a cohesive whole.

Detailed Content

Value Semantics and Immutability

Swift uses var for mutable variables and let for immutable constants. struct is a value type—assignment automatically copies:

struct User {
    let username: String
    var isVisible: Bool = true
    var friends: [String] = []
}

var alice = User(username: "alice")
alice.friends = ["charlie"]

var bruno = User(username: "bruno")
bruno.friends = alice.friends

alice.friends.append("dash")
bruno.friends  // ["charlie"], unaffected by alice's modification

Key points:

  • let username means immutable after creation; var friends allows modification
  • bruno.friends = alice.friends copies the entire array—two variables are independent
  • struct composed of value types automatically gets value semantics—the foundation of Swift concurrency safety (03:04)

Error Handling and Optional

Swift distinguishes three exception cases: recoverable errors use throw, possibly absent values use Optional, program logic errors use precondition/fatalError.

struct User {
    let username: String
    var isVisible: Bool = true
    public private(set) var friends: [String] = []

    mutating func addFriend(username: String) throws {
        guard username != self.username else {
            throw SocialError.befriendingSelf
        }
        guard !friends.contains(username) else {
            throw SocialError.duplicateFriend(username: username)
        }
        friends.append(username)
    }
}

enum SocialError: Error {
    case befriendingSelf
    case duplicateFriend(username: String)
}

Key points:

  • mutating marks methods that modify struct properties (03:05)
  • guard detects error conditions—must exit the function when not satisfied
  • throws marks functions that may throw errors; callers must explicitly mark with try
  • SocialError.duplicateFriend(username: username) carries context in associated values—callers know “which user was duplicated”
  • private(set) makes friends publicly readable but privately writable—forces external modification through addFriend

Optional handles “value may not exist” scenarios—Dictionary subscript access naturally returns Optional:

func findUser(_ username: String) -> User? {
    allUsers[username]
}

if let charlie = findUser("charlie") {
    print("Found \(charlie)")
} else {
    print("charlie not found")
}

Key points:

  • User? is Optional type—either User or nil
  • if let safely unwraps—enters branch only when non-nil
  • Force unwrap ! crashes on nil—use only when 100% certain

Protocols and Generics

Protocols abstract without inheritance hierarchies; generics make algorithms work for any type meeting constraints:

extension UserStore {
    public func friendsOfFriends(_ username: String) throws -> [String] {
        let user = try user(for: username)
        let excluded = Set(user.friends + [username])
        return user.friends
            .compactMap { lookUpUser($0) }      // [String] -> [User]
            .flatMap { $0.friends }             // [User] -> [String]
            .filter { !excluded.contains($0) }  // drop excluded
            .uniqued()
    }
}

extension Collection where Element: Hashable {
    func uniqued() -> [Element] {
        let unique = Set(self)
        return Array(unique)
    }
}

Key points:

  • compactMap filters nil and unwraps—more concise than map + filter (16:13)
  • flatMap flattens nested arrays
  • uniqued() is a custom extension constrained by where Element: Hashable—only applies to Collections with hashable elements
  • Protocols + generics make this algorithm work for Array, Set, Dictionary Keys, and any qualifying collection type

Concurrency: actor and Sendable

Swift 6 verifies data race safety at compile time. Shared mutable state must be protected with actor:

actor UserStore {
    var allUsers: [String: User] = [:]
}

router.get("friendsOfFriends") { request, context -> [String] in
    let username = try request.queryArgument(for: "username")
    return try await UserStore.shared.friendsOfFriends(username)
}

Key points:

  • actor is a reference type that automatically serializes access—only one Task executes actor methods at a time (22:24)
  • Calling methods from outside an actor requires await—the caller may suspend while waiting
  • Sendable protocol marks types safely passed across concurrency domains; value types (struct) naturally satisfy Sendable more easily

Core Takeaways

  1. Model data with value semantics: Default to struct when defining new types. Value type assignment automatically copies, naturally isolating state—safer in concurrent scenarios. Use class or actor only when you need reference semantics (shared mutable state, identity, inheritance). How to start: review existing classes—if there’s no shared state need, convert to struct.

  2. Use guard + throws instead of implicit error handling: guard statements intercept invalid input at function entry; throws makes error propagation paths visible in type signatures. Callers must mark with try—the compiler ensures you don’t miss error handling. How to start: convert direct array/dictionary modification code into mutating methods with guard validation and throws marking.

  3. Use actor instead of manual locking: Shared mutable state easily causes data races in concurrent environments. actor automatically serializes access; the compiler checks Sendable constraints—more reliable than manual locking. How to start: find code using class + lock for shared state, replace with actor, let the compiler verify concurrency safety.

Comments

GitHub Issues · utterances