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 usernamemeans immutable after creation;var friendsallows modificationbruno.friends = alice.friendscopies 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:
mutatingmarks methods that modify struct properties (03:05)guarddetects error conditions—must exit the function when not satisfiedthrowsmarks functions that may throw errors; callers must explicitly mark withtrySocialError.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 throughaddFriend
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—eitherUserornilif letsafely 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:
compactMapfilters nil and unwraps—more concise thanmap+filter(16:13)flatMapflattens nested arraysuniqued()is a custom extension constrained bywhere 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:
actoris 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 Sendableprotocol marks types safely passed across concurrency domains; value types (struct) naturally satisfy Sendable more easily
Core Takeaways
-
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.
-
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.
-
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.
Related Sessions
- Embrace Swift generics — Deep dive into generic constraints and protocol design—a natural extension of this session’s protocols and generics section
- Explore structured concurrency in Swift — Systematic coverage of Task, async/await, and the actor concurrency model
- Demystify explicitly built modules — Xcode 16 build system changes involving Swift module compilation
- Expand on Swift macros — Compile-time code generation with macros—another language extension beyond property wrappers and result builders
Comments
GitHub Issues · utterances