Highlight
Swift 6.3 and 6.4 improve everyday coding ergonomics (
anyAppleOSavailability, module selectors,@diagnosewarning control), expand the standard library (task cancellation shields,Dictionary.mapKeyedValues,FilePath), add two-way interoperability between Swift Testing and XCTest, broaden cross-platform interoperability (@Cexport to C, Swift-Java, Wasm), and significantly extend the ownership system (Iterable,borrow/mutateaccessors,Ref/MutableRef) so high-performance code is easier to write without giving up safety.
Core Ideas
The small annoyances in daily coding are disappearing
Some details in Swift have always required a few extra keystrokes. For example, when some or any was combined with an optional type, you had to add parentheses: (any Protocol)?. Swift 6.4 removes that rule, and the compiler now understands your intent automatically. (00:52)
When an error thrown from a concurrent task was silently ignored, older compilers gave no hint. The compiler now emits a warning that tells you to either catch the error inside the task or store the task and inspect it later. (01:12) The old limitation on calling async functions inside defer blocks has also been lifted. (01:22)
Sendable checking has improved too. A class that previously needed @unchecked Sendable because it contained a weak var can now use weak let instead; immutability lets it pass Sendable checking. (01:27) If a type explicitly should not support Sendable, you can declare that with the new ~Sendable syntax, while subclasses can still independently declare Sendable conformance. (01:38)
Say goodbye to verbose platform availability annotations
Apple’s ecosystem has grown from two platforms to five, and availability annotations grew with it. Previously, a new API had to list five platforms:
@available(macOS 27, iOS 27, watchOS 27, tvOS 27, visionOS 27, *)
func showStatus() { ... }
Swift 6.4 introduces anyAppleOS, reducing that to one line: (02:17)
@available(anyAppleOS 27, *)
func showStatus() { ... }
@available(anyAppleOS 27, *)
@available(tvOS, unavailable)
func launch() { ... }
#if os(anyAppleOS)
func makeLiveActivityWidget() -> some Widget { ... }
#endif
When a platform is an exception, use anyAppleOS for the default and then override it with a specific platform attribute. #if os(anyAppleOS) also works for conditional compilation.
Precisely control compiler warnings
During API evolution, old APIs are marked deprecated, but sometimes you cannot migrate immediately. The @diagnose attribute gives fine-grained control over warnings on a specific declaration: (02:40)
@diagnose(DeprecatedDeclaration, as: ignored, reason: "Flying with surplus hardware")
func makeApolloSoyuzMission() -> Mission { ... }
@diagnose(StrictMemorySafety, as: warning)
func uplinkCommand(from receiver: inout Receiver, to computer: inout Computer) { ... }
@diagnose(ErrorInFutureSwiftVersion, as: error)
func fetchPosition() -> (x: Double, y: Double, z: Double) { ... }
Key points:
as: ignoreddisables a specific warning without affecting the rest of the projectas: warningenables warning groups that are off by default, such asStrictMemorySafetyas: errorpromotes warnings that will become errors in a future Swift version into errors today
Resolve module name conflicts
When two modules export types with the same name, ModuleName.TypeName can fail if the module itself also contains a type with the module’s name. Swift resolves it as a type name first, not a module name. Swift 6.3 introduces the module selector ::, whose left side is always a module name: (03:47)
import Rocket
import GiftShopToys
let rocket1 = SaturnV() // Ambiguous error
let rocket2 = Rocket.SaturnV() // If the Rocket module also has a Rocket type, this can resolve incorrectly
let rocket3 = Rocket::SaturnV() // Correctly points to SaturnV in the Rocket module
The module selector also works for method-name conflicts: (05:00)
launchPadTechnician.HumanResources::fire()
Use it defensively in macro expansions and generated code, because you do not know what modules the target project imports.
Details
New standard library tools
Task cancellation shields
Checking task cancellation matters, but some operations must finish even if the task has been cancelled, such as completing a disk write to avoid file corruption. withTaskCancellationShield makes cancellation checks inside the shielded region always return false: (06:40)
extension EmergencyTransponder {
func sendSOS() {
withTaskCancellationShield {
radio.send(makeSOSPacket())
}
}
}
Key points:
Task.isCancelledis alwaysfalseinside the shielded region- Keep the region short and focused on completing or rolling back work that has already started
Dictionary.mapKeyedValues
mapValues receives only the old value. If you need the key to compute the new value, you previously had to construct the dictionary manually. mapKeyedValues now gives the closure both key and value: (07:06)
func makeCalendarDisplayNames(for missions: [Mission: LaunchWindow]) -> [Mission: String] {
missions.mapKeyedValues { mission, launchWindow in
makeDisplayName(for: mission, in: launchWindow)
}
}
FilePath
Different platforms represent file paths with subtle differences. The Swift standard library adds a FilePath type based on Swift System, correctly handling macOS named resources: (07:14)
var path: FilePath = "/var/www/static"
path.components.append("WWDC")
print(path.components)
// ["var", "www", "static", "WWDC"]
var path2: FilePath = "/var/www/static/..namedresource/rsrc"
print(path2.components)
// ["var", "www", "static"]
Swift Testing improvements
Issue severity and dynamic cancellation
Tests can record non-fatal issues without blocking CI: (07:41)
@Test(arguments: allRockets)
func testBurn(rocket: Rocket) throws {
rocket.burn(for: .seconds(150))
let remaining = rocket.propellantKg / rocket.totalPropellantKg
if remaining < 0.10 {
Issue.record(
"\(rocket.name) remaining fuel is below 10% reserve target",
severity: .warning
)
}
#expect(remaining > 0.02, "\(rocket.name) propellant critically low - abort")
}
Parameterized tests can dynamically skip unsuitable arguments: (07:52)
@Test(arguments: allRockets)
func testBurn(rocket: Rocket) throws {
if rocket.engineType == .solid {
try Test.cancel("\(rocket.name) has solid fuel")
}
// ...
}
Two-way XCTest interoperability
In Swift 6.4, failures from XCTest assertions called inside Swift Testing are reported as test issues instead of immediately crashing. Conversely, calling #expect inside an XCTestCase also works normally: (08:34)
// Call XCTest from Swift Testing
func checkedTransmitAndReceive(on radio: Radio,
packet: Packet,
expectedByteCount: Int) throws -> [UInt8] {
try radio.transmit(bytes: packet.data)
let bytes = try radio.receive()
XCTAssertEqual(bytes.count, expectedByteCount)
return bytes
}
@Test
func pingTest() throws {
let radio = Radio()
let bytes = try checkedTransmitAndReceive(on: radio, packet: .ping, expectedByteCount: 8)
#expect(bytes == [0x00, 0x00, 0xf0, 0x37, 0x0f, 0xc7, 0x00, 0x01])
}
// Call Swift Testing from XCTest
class RadioTests: XCTestCase {
func testPingPacketTransmission() {
let radio = Radio()
let bytes = try checkedTransmitAndReceive(on: radio,
packet: .ping,
expectedByteCount: 8)
#expect(bytes == [0x00, 0x00, 0xf0, 0x36, 0x0f, 0xc7, 0x00, 0x02])
}
}
The default report level is warning, and it can be upgraded to test failure in Xcode build settings.
Subprocess 1.0
The Subprocess package has officially reached 1.0. The API is simpler, and cross-platform support is much stronger. Output streams are accessed through AsyncBufferSequence, ensuring each stream is created only once: (10:01)
let result = try await Subprocess.run(.name("ls"),
input: .none,
output: .sequence,
error: .string(limit: 4096)) { execution in
execution.standardOutput.strings().filter { $0.hasSuffix(".obj") }
}
for try await objectFile in result.closureOutput {
print("Object file: \(objectFile)")
}
Key points:
strings()reads output line by line and correctly handles grapheme cluster boundaries- Platform-specific process file descriptors and termination statuses are supported
Foundation: ProgressManager
ProgressManager is a new Foundation progress-reporting type designed for the async/await concurrency model. It separates progress composition from progress reporting and supports type-safe metadata attachment: (10:37)
let manager = ProgressManager(totalCount: 100)
try await rocket.launch(mission.subprogress(assigningCount: 100))
// Observe progress
Task {
for await update in Observations({ mission.fractionCompleted }) {
print("Mission \(Int(update * 100))%")
}
}
// Attach metadata
extension Rocket {
func ascend(_ progress: consuming Subprogress) async throws {
let stage = progress.start(totalCount: 3)
stage.deltaV = 3_400; try await burn(); stage.complete(count: 1)
stage.deltaV = 2_100; try await stageSeparation(); stage.complete(count: 1)
stage.deltaV = 1_800; try await coast(); stage.complete(count: 1)
}
}
print("Δv to orbit: \(mission.summary(of: \.deltaV)) m/s")
Cross-platform interoperability
Export Swift functions to C
Swift 6.4 can expose Swift functions to C with the @C attribute, similar to @objc but aimed at C interoperability: (12:39)
@c
@implementation
func launchWindowLength(_ window: Span<LaunchWindow>) -> TimeInterval {
window[0].end.timeIntervalSince(window[0].start)
}
Key points:
- Use
@cand@implementationtogether to implement functions declared in a C header - Swift
Spanautomatically bridges to C arrays - The compiler prevents passing C-incompatible types
Swift-Java interoperability
The Swift-Java package now supports calling Swift async and throwing functions from Java. The generic system supports constrained extensions and Java classes conforming to Swift protocols. The official Swift SDK for Android is now available from swift.org. (15:14)
WebAssembly support
The open-source Swift toolchain can now compile to WebAssembly. The JavaScriptKit project makes bridging between Swift and JavaScript safer and faster. The Goodnotes team found that safe bridging was 35-40 times faster than dynamic bridging. (16:40)
Embedded Swift expansion
Embedded Swift adds support for existential types and untyped throws. Debugging metadata is stored in DWARF instead of the binary itself, greatly reducing size while improving core-dump debugging. (18:08)
Performance tuning: control optimizer decisions
Inlining control
Swift 6.4 adds @inline(always), paired with the existing @inline(never): (21:30)
@inline(never)
func makeInts(randomized: Bool) -> [256 of Int] { ... }
@inline(always)
func makeInts(randomized: Bool) -> [256 of Int] { ... }
Key points:
@inline(never)forbids inlining and is useful when code size matters@inline(always)forces inlining and works best withfinalclass methods- Virtual method calls may still be impossible to inline
Generic specialization
The @specialized attribute lets library authors tell the compiler to generate specialized versions for particular types: (22:17)
@specialized(where Values == [UInt8])
func histogram<Values>(of values: Values) -> [256 of Int] where Values: Sequence<UInt8> {
var result = makeInts(randomized: false)
for value in values {
result[Int(value)] += 1
}
return result
}
Key points:
- Write a
whereclause in the attribute to constrain generic parameters - The compiler generates specialized versions for matching constraints and removes generic overhead
- This is useful for generic functions frequently called with specific types
Ownership system expansion
Iterable protocol
In Swift 6.4, for loops support the new Iterable protocol. Unlike Sequence, Iterable accesses elements through borrowing, avoiding data copies and reference counting: (26:18)
protocol Iterable<Element, Failure>: ~Copyable, ~Escapable {
associatedtype Element: ~Copyable
associatedtype IterableIterator: IterableIteratorProtocol<Element, Failure>, ~Copyable, ~Escapable
associatedtype Failure: Error = Never
func makeIterableIterator() -> IterableIterator
var underestimatedCount: Int { get }
}
Key points:
forloops preferSequenceand fall back toIterable- Non-copyable elements are supported
- Typed throws are supported
- Mutating the
Iterableduring iteration is forbidden and enforced by exclusivity checking - Elements are returned in batches through
Span, improving efficiency
borrow and mutate accessors
get/set accessors work by copying data. For large value types, such as an InlineArray of 256 Int values at about 2 KB, modifying one element copies the whole structure twice. The new borrow and mutate accessors operate directly on the original storage: (28:19)
@safe public struct UniqueBox<Value: ~Copyable>: ~Copyable {
private let valuePointer: UnsafeMutablePointer<Value>
public init(_ value: consuming Value) { ... }
public var value: Value {
borrow { valuePointer.pointee }
mutate { &valuePointer.pointee }
}
}
Key points:
borrowprovides read-only shared access without copyingmutateprovides exclusive write access and mutates in place- Non-copyable value types are supported
UniqueBoxhas joined the standard library
Ref and MutableRef
Ref is similar to Span, but for a single value. It can be stored in a variable, passed to functions, and returned from functions: (29:42)
func updateCount<Key: Hashable>(
for key: Key,
from sets: [Set<Key>],
in counts: inout [Key: Int]
) {
var countRef = MutableRef(&counts[key, default: 0])
for set in sets {
if set.contains(key) {
countRef.value += 1
}
}
}
Key points:
MutableRef(&expression)creates a reference from write access using the prefix&- Repeated dictionary lookups can be moved outside the loop
Refis non-escapable, so the compiler knows the access ends when the variable leaves scope- This avoids older, obscure tricks that required
inoutparameters plus nested functions
Other new types:
UniqueArray: a non-copyable dynamic array with no reference-counting overheadwithTemporaryAllocation: replacesUnsafeMutableBufferPointerwithOutputSpanContinuation: compile-time checking thatresumeis called exactly once, combining the safety ofCheckedContinuationwith the efficiency ofUnsafeContinuation
Key Takeaways
1. Use anyAppleOS to simplify multi-platform code
What to do: Replace all five-platform @available annotations and #if os conditional-compilation checks in your project with anyAppleOS.
Why it is worth doing: It reduces boilerplate and lowers the maintenance cost of adding new platforms.
How to start: Search for patterns like @available(macOS and #if os(macOS) || os(iOS) and replace them in batches.
2. Use @diagnose to manage technical debt gradually
What to do: Add @diagnose(DeprecatedDeclaration, as: ignored) to deprecated API call sites that cannot be migrated yet, instead of disabling warnings globally.
Why it is worth doing: It precisely limits warning suppression and avoids missing deprecated calls that truly need attention.
How to start: Filter deprecated warnings in Xcode’s Issue Navigator and evaluate each call site before adding @diagnose.
3. Use Iterable to optimize large-data traversal
What to do: Implement Iterable for custom collection types instead of Sequence.
Why it is worth doing: It avoids copying and reference-counting overhead when traversing large collections and supports non-copyable elements.
How to start: Define the iterator returned by makeIterableIterator() and implement nextSpan(maximumCount:) to return Span<Element> batches.
4. Use borrow/mutate accessors to optimize large value-type properties
What to do: Replace get/set with borrow/mutate on containers that wrap large value types, such as caches or configuration stores.
Why it is worth doing: It eliminates implicit copies during large value-type property access and operates directly on underlying storage.
How to start: Replace accessor keywords in computed properties on custom types, using UniqueBox or your own pointer management where appropriate.
5. Use MutableRef to remove repeated dictionary lookups
What to do: When a loop repeatedly modifies the same dictionary key, use MutableRef to hoist the lookup out of the loop.
Why it is worth doing: It reduces O(n) repeated hash lookups and improves hot-path performance.
How to start: Find code that repeatedly accesses dict[key] inside a loop, extract var ref = MutableRef(&dict[key, default: 0]), and use ref.value inside the loop.
Related Sessions
- 267 - Swift Testing — A deeper look at Swift Testing migration strategies and advanced usage
- 269 - SwiftUI — How SwiftUI’s new features work with Swift language improvements
- 328 - MLX Swift — Practical use of Swift’s ownership system in high-performance computing
- 258 - Xcode 27 — New Xcode 27 features and Swift development workflow improvements
- 289 - AppKit modernization — Applying new Swift language features in AppKit modernization
Comments
GitHub Issues · utterances