WWDC Quick Look 💓 By SwiftGGTeam
What's new in Swift

What's new in Swift

Watch original video

Highlight

Swift 6 introduces a new language mode with compile-time data-race safety guarantees, alongside Embedded Swift—a subset for highly constrained embedded systems.


Core Content

2024 marks Swift’s tenth anniversary. From its WWDC 2014 debut, to open-sourcing and Linux support in 2015, through Swift 5’s stable ABI, the async/await concurrency model, bidirectional C++ interop, and Macros—Swift has come a long way. Swift 6 marks a comprehensive upgrade in portability, performance, and developer experience.

Swift 6’s most significant change is the new language mode: compile-time data-race safety guarantees. Data races are a common concurrency trap—when multiple threads access mutable data simultaneously and one tries to modify it, behavior is undefined. Swift 5.10 required the complete concurrency checking flag for this guarantee; Swift 6 language mode makes it the default. Passing non-Sendable values across actor boundaries becomes a compile error, not a runtime hazard. More importantly, Swift 6’s data-race detection is smarter: when a non-Sendable value is passed to another actor and the original isolation domain no longer references it, the compiler recognizes this as safe. This improvement greatly reduces false positives, making migration smoother.

Another important direction is Embedded Swift. This is a language subset for highly constrained systems, generating extremely small standalone binaries (just a few KB). By disabling language features requiring runtime support like reflection and any types, combined with full generic specialization and static linking, Embedded Swift runs on ARM and RISC-V microcontrollers, even Apple security coprocessors. For developers accustomed to C/C++ embedded development, Embedded Swift offers a safer alternative while maintaining C/C++ interoperability for gradual migration.


Detailed Content

Swift 6 Language Mode and Data-race Safety

The core goal of Swift 6 language mode is making data-race safety the default. The following example shows passing a non-Sendable type across actor boundaries (28:02):

class Client {
  init(name: String, balance: Double) {}
}

actor ClientStore {
  static let shared = ClientStore()
  private var clients: [Client] = []
  func addClient(_ client: Client) {
    clients.append(client)
  }
}

@MainActor
func openAccount(name: String, balance: Double) async {
  let client = Client(name: name, balance: balance)
  await ClientStore.shared.addClient(client)
}

Key points:

  • The Client class is not marked Sendable, so cross-actor passing is theoretically unsafe
  • Under Swift 5.10 complete concurrency checking, this produces a warning
  • Swift 6 compiler recognizes that after client is passed to ClientStore, MainActor no longer references it, so there is no data race
  • If client is used again after passing, the compiler reports an error

Swift 6 also introduces new low-level synchronization primitives. The Atomic type provides lock-free atomic operations (28:52):

import Dispatch
import Synchronization

let counter = Atomic<Int>(0)

DispatchQueue.concurrentPerform(iterations: 10) { _ in
  for _ in 0 ..< 1_000_000 {
    counter.wrappingAdd(1, ordering: .relaxed)
  }
}

print(counter.load(ordering: .relaxed))

Key points:

  • Atomic is a generic type using efficient lock-free implementations where the platform supports them
  • Must be stored in a let property to ensure safe concurrent access
  • All operations require explicit memory ordering parameters, similar to the C/C++ memory model

The Mutex type provides mutual exclusion (29:21):

import Synchronization

final class LockingResourceManager: Sendable {
  let cache = Mutex<[String: Resource]>([:])

  func save(_ resource: Resource, as key: String) {
    cache.withLock {
      $0[key] = resource
    }
  }
}

Key points:

  • Mutex should also be stored in a let property
  • withLock ensures mutually exclusive access to protected storage
  • The lock is held for the entire closure execution

Noncopyable Types

Swift 6 expands noncopyable type support to generic contexts like Optional (17:50):

struct File: ~Copyable {
  private let fd: CInt

  init?(name: String) {
    guard let fd = open(name) else {
      return nil
    }
    self.fd = fd
  }

  func write(buffer: [UInt8]) {
    // ...
  }

  deinit {
    close(fd)
  }
}

Key points:

  • ~Copyable indicates the type cannot be copied, suitable for unique ownership scenarios
  • Swift 5.10 only supported concrete types; Swift 6 supports generic contexts
  • You can now write failable initializers returning Optional<File>
  • deinit ensures the file descriptor closes when scope ends

Typed Throws

Swift 6 introduces typed throws, letting functions specify their error type (23:43 vs 24:19):

enum IntegerParseError: Error {
  case nonDigitCharacter(String, index: String.Index)
}

// Traditional throws, with type erasure
func parse(string: String) throws -> Int {
  for index in string.indices {
    throw IntegerParseError.nonDigitCharacter(string, index: index)
  }
}

// Typed throws, preserving the type
func parse(string: String) throws(IntegerParseError) -> Int {
  for index in string.indices {
    throw IntegerParseError.nonDigitCharacter(string, index: index)
  }
}

// When used, error is directly of type IntegerParseError
do {
  let value = try parse(string: "1+234")
}
catch {
  // The type of error is IntegerParseError, not any Error
}

Key points:

  • throws(ErrorType) syntax specifies the thrown error type
  • No type erasure; error in catch blocks keeps its concrete type
  • throws is equivalent to throws(any Error); non-throwing functions equivalent to throws(Never)
  • Suitable for internal functions or constrained environments (no runtime allocation)

Cross-platform and Static Linux SDK

Swift 6 introduces a new static Linux SDK supporting cross-compilation from macOS to Linux (9:15):

# Build the macOS version
swift build

# Install the static Linux SDK
swift sdk install ~/preview-static-swift-linux-0.0.1.tar.gz

# Cross-compile to ARM64 Linux with static linking
swift build --swift-sdk aarch64-swift-linux-musl

# Check the output
file .build/debug/CatService

# Deploy to Linux
scp .build/debug/CatService demo-linux-host:~/CatService

# Run on Linux without installing the Swift runtime
./CatService

Key points:

  • Static linking means the binary runs on any Linux machine
  • No need to install Swift runtime or dependency libraries on the target machine
  • musl is a lightweight C standard library for static linking
  • Supports new Linux platforms including Fedora and Debian

Swift Testing

The new Swift Testing framework provides a more modern testing experience (13:50):

import Testing

@Test("Recognized rating", .tags(.critical))
func rating(videoId: Int, videoName: String, expectedRating: String) {
    let video = Video(id: videoId, name: videoName)
    #expect(video.rating == expectedRating)
}

Key points:

  • @Test macro marks test functions with customizable display names
  • .tags() organizes and filters tests
  • Parameterized tests avoid duplicating code for multiple inputs
  • #expect macro supports arbitrary Swift expressions

Core Takeaways

1. Migrate gradually to Swift 6 language mode

Don’t rush to switch the entire project. First enable concurrency checking incrementally in Build Settings (SWIFT_STRICT_CONCURRENCY set to minimal or targeted), fixing each batch before raising strictness. Swift 6’s smart data-race checking greatly reduces false positives for smoother migration. For types you don’t want to modify yet, the compiler provides tools to help.

2. Consider Embedded Swift for resource-constrained scenarios

If your project involves microcontrollers, embedded systems, or needs extremely small binary sizes, Embedded Swift offers a safer alternative to C/C++. Playdate game consoles can run Swift games of just a few KB; Apple security coprocessors use it too. With C++ interop, you can gradually introduce Swift into existing embedded code.

3. Use the static Linux SDK to simplify deployment

Develop on macOS, deploy to Linux—statically linked binaries need no Swift runtime on the target machine. Very friendly for containerized deployment and CI/CD. One swift build --swift-sdk aarch64-swift-linux-musl generates an executable that runs anywhere.


Comments

GitHub Issues · utterances