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
Clientclass is not markedSendable, so cross-actor passing is theoretically unsafe - Under Swift 5.10 complete concurrency checking, this produces a warning
- Swift 6 compiler recognizes that after
clientis passed toClientStore, MainActor no longer references it, so there is no data race - If
clientis 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:
Atomicis a generic type using efficient lock-free implementations where the platform supports them- Must be stored in a
letproperty 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:
Mutexshould also be stored in aletpropertywithLockensures 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:
~Copyableindicates 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> deinitensures 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
throwsis equivalent tothrows(any Error); non-throwing functions equivalent tothrows(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
muslis 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:
@Testmacro marks test functions with customizable display names.tags()organizes and filters tests- Parameterized tests avoid duplicating code for multiple inputs
#expectmacro 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.
Related Sessions
- A Swift Tour: Explore Swift’s features and design — Introduction to Swift language core features and design philosophy
- Migrate your app to Swift 6 — Practical guide to migrating existing apps to Swift 6 language mode
- Meet Swift Testing — Using the new Swift Testing framework and best practices
- Go small with Embedded Swift — Using Embedded Swift on microcontrollers and resource-constrained systems
- Mix Swift and C++ — Deep dive into bidirectional Swift and C++ interop (2023)
Comments
GitHub Issues · utterances