Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2023 Developer ToolsEssentialsSwift

WWDC23 · 43 min · Developer Tools / Essentials / Swift

What’s new in Swift

Join us for an update on Swift. We’ll show you how APIs are becoming more extensible and expressive with features like parameter packs and macros. We’ll also take you through improvements to interoperability and share how we’re expanding Swift’s performance and safety benefits everywhere from Foundation to large-scale distributed programs on the server.

Watch at developer.apple.com ↗

Transcript all transcripts

Chapters

  • 0:39 — Swift project update
  • 2:44 — Using if/else and switch statements as expressions
  • 3:52 — Result builders
  • 4:53 — type parameter packs
  • 9:34 — Swift macros
  • 19:47 — Swift foundation
  • 23:25 — Ownership
  • 27:59 — C++ interoperability
  • 32:41 — What's new in Swift Concurrency
  • 38:20 — FoundationDB: A case study

Code shown on screen · 53 snippets

Hard-to-read compound ternary expression swift · at 3:06 ↗
let bullet =
    isRoot && (count == 0 || !willExpand) ? ""
        : count == 0    ? "- "
        : maxDepth <= 0 ? "▹ " : "▿ "
Familiar and readable chain of if statements swift · at 3:19 ↗
let bullet =
    if isRoot && (count == 0 || !willExpand) { "" }
    else if count == 0 { "- " }
    else if maxDepth <= 0 { "▹ " }
    else { "▿ " }
Initializing a global variable or stored property swift · at 3:30 ↗
let attributedName = AttributedString(markdown: displayName)
In 5.9, if statements can be an expression swift · at 3:46 ↗
let attributedName = 
				if let displayName, !displayName.isEmpty {
            AttributedString(markdown: displayName)
        } else {
            "Untitled"
        }
In Swift 5.7, errors may appear in a different place swift · at 4:31 ↗
struct ContentView: View {
    enum Destination { case one, two }

    var body: some View {
        List {
            NavigationLink(value: .one) { // The issue actually occurs here
                Text("one")
            }
            NavigationLink(value: .two) {
                Text("two")
            }
        }.navigationDestination(for: Destination.self) {
            $0.view // Error occurs here in 5.7
        }
    }
}
In Swift 5.9, you now receive a more accurate compiler diagnostic swift · at 4:47 ↗
struct ContentView: View {
    enum Destination { case one, two }

    var body: some View {
        List {
            NavigationLink(value: .one) { //In 5.9, Errors provide a more accurate diagnostic
                Text("one")
            }
            NavigationLink(value: .two) {
                Text("two")
            }
        }.navigationDestination(for: Destination.self) {
            $0.view // Error occurs here in 5.7
        }
    }
}
An API that takes a request type and evaluates it to produce a strongly typed value swift · at 5:47 ↗
struct Request<Result> { ... }

struct RequestEvaluator {
    func evaluate<Result>(_ request: Request<Result>) -> Result
}

func evaluate(_ request: Request<Bool>) -> Bool {
    return RequestEvaluator().evaluate(request)
}
APIs that abstract over concrete types and varying number of arguments swift · at 6:03 ↗
let value = RequestEvaluator().evaluate(request)

let (x, y) = RequestEvaluator().evaluate(r1, r2)

let (x, y, z) = RequestEvaluator().evaluate(r1, r2, r3)
Writing multiple overloads for the evaluate function swift · at 6:35 ↗
func evaluate<Result>(_:) -> (Result)

func evaluate<R1, R2>(_:_:) -> (R1, R2)

func evaluate<R1, R2, R3>(_:_:_:) -> (R1, R2, R3)

func evaluate<R1, R2, R3, R4>(_:_:_:_:)-> (R1, R2, R3, R4)

func evaluate<R1, R2, R3, R4, R5>(_:_:_:_:_:) -> (R1, R2, R3, R4, R5)

func evaluate<R1, R2, R3, R4, R5, R6>(_:_:_:_:_:_:) -> (R1, R2, R3, R4, R5, R6)
Overloads create an arbitrary upper bound for the number of arguments swift · at 6:47 ↗
//This will cause a compiler error "Extra argument in call"
let results = evaluator.evaluate(r1, r2, r3, r4, r5, r6, r7)
Individual type parameter swift · at 7:12 ↗
<each Result>
Collapsing the same set of overloads into one single evaluate function swift · at 7:36 ↗
func evaluate<each Result>(_: repeat Request<each Result>) -> (repeat each Result)
Calling updated evaluate function looks identical to calling an overload swift · at 8:21 ↗
struct Request<Result> { ... }

struct RequestEvaluator {
    func evaluate<each Result>(_: repeat Request<each Result>) -> (repeat each Result)
}

let results = RequestEvaluator.evaluate(r1, r2, r3)
It isn't clear why an assert function fails swift · at 10:01 ↗
assert(max(a, b) == c)
XCTest provides an assert-equal operation swift · at 10:20 ↗
XCAssertEqual(max(a, b), c) //XCTAssertEqual failed: ("10") is not equal to ("17")
Assert as a macro swift · at 11:02 ↗
#assert(max(a, b) == c)
Macros are distributed as packages swift · at 11:42 ↗
import PowerAssert
#assert(max(a, b) == c)
Macro declaration for assert swift · at 12:07 ↗
public macro assert(_ condition: Bool)
Uses are type checked against the parameters swift · at 12:26 ↗
import PowerAssert
#assert(max(a, b)) //Type 'Int' cannot be a used as a boolean; test for '!= 0' instead
A macro definition swift · at 12:52 ↗
public macro assert(_ condition: Bool) = #externalMacro(
    module: “PowerAssertPlugin”,
    type: “PowerAssertMacro"
)
Swift compiler passes the source code for the use of the macro swift · at 13:11 ↗
#assert(a == b)
Compiler plugin produces new source code, which is integrated back into the Swift program swift · at 13:14 ↗
PowerAssert.Assertion(
    "#assert(a == b)"
) {
    $0.capture(a, column: 8) == $0.capture(b, column: 13)
}
Macro declarations include roles swift · at 13:33 ↗
// Freestanding macro roles

@freestanding(expression)
public macro assert(_ condition: Bool) = #externalMacro(
    module: “PowerAssertPlugin”,
    type: “PowerAssertMacro"
)
New Foundation Predicate APIs uses a `@freestanding(expression)` macro role swift · at 13:53 ↗
let pred = #Predicate<Person> {
    $0.favoriteColor == .blue
}

let blueLovers = people.filter(pred)
Predicate expression macro swift · at 14:14 ↗
// Predicate expression macro

@freestanding(expression) 
public macro Predicate<each Input>(
    _ body: (repeat each Input) -> Bool
) -> Predicate<repeat each Input>
Example of a commonly used enum swift · at 14:48 ↗
enum Path {
    case relative(String)
    case absolute(String)
}
Checking a specific case, like when filtering all absolute paths swift · at 15:01 ↗
let absPaths = paths.filter { $0.isAbsolute }
Write an `isAbsolute` check as a computer property... swift · at 15:09 ↗
extension Path {
    var isAbsolute: Bool {
        if case .absolute = self { true }
        else { false }
    }
}
...And another for `isRelative` swift · at 15:12 ↗
extension Path {
    var isRelative: Bool {
        if case .relative = self { true }
        else { false }
    }
}
Augmenting the enum with an attached macro swift · at 15:17 ↗
@CaseDetection
enum Path {
    case relative(String)
    case absolute(String)
}

let absPaths = paths.filter { $0.isAbsolute }
Macro-expanded code is normal Swift code swift · at 15:36 ↗
enum Path {
    case relative(String)
    case absolute(String)
  
    //Expanded @CaseDetection macro integrated into the program.
    var isAbsolute: Bool {
        if case .absolute = self { true }
        else { false }
    }

    var isRelative: Bool {
        if case .relative = self { true }
        else { false }
    }
}
Observation in SwiftUI prior to 5.9 swift · at 16:57 ↗
// Observation in SwiftUI

final class Person: ObservableObject {
    @Published var name: String
    @Published var age: Int
    @Published var isFavorite: Bool
}

struct ContentView: View {
    @ObservedObject var person: Person
    
    var body: some View {
        Text("Hello, \(person.name)")
    }
}
Observation now swift · at 17:25 ↗
// Observation in SwiftUI

@Observable final class Person {
    var name: String
    var age: Int
    var isFavorite: Bool
}

struct ContentView: View {
    var person: Person
    
    var body: some View {
        Text("Hello, \(person.name)")
    }
}
Observable macro works with 3 macro roles swift · at 17:42 ↗
@attached(member, names: ...)
@attached(memberAttribute)
@attached(conformance)
public macro Observable() = #externalMacro(...).
Unexpanded macro swift · at 17:52 ↗
@Observable final class Person {
    var name: String
    var age: Int
    var isFavorite: Bool
}
Expanded member attribute role swift · at 18:05 ↗
@Observable final class Person {
    var name: String
    var age: Int
    var isFavorite: Bool
  
		internal let _$observationRegistrar = ObservationRegistrar<Person>()
    internal func access<Member>(
        keyPath: KeyPath<Person, Member>
    ) {
        _$observationRegistrar.access(self, keyPath: keyPath)
    }
    internal func withMutation<Member, T>(
        keyPath: KeyPath<Person, Member>,
        _ mutation: () throws -> T
    ) rethrows -> T {
        try _$observationRegistrar.withMutation(of: self, keyPath: keyPath, mutation)
    }
}
Member attribute role adds `@ObservationTracked` to stored properties swift · at 18:12 ↗
@Observable final class Person {
    @ObservationTracked var name: String
    @ObservationTracked var age: Int
    @ObservationTracked var isFavorite: Bool
  
		internal let _$observationRegistrar = ObservationRegistrar<Person>()
    internal func access<Member>(
        keyPath: KeyPath<Person, Member>
    ) {
        _$observationRegistrar.access(self, keyPath: keyPath)
    }
    internal func withMutation<Member, T>(
        keyPath: KeyPath<Person, Member>,
        _ mutation: () throws -> T
    ) rethrows -> T {
        try _$observationRegistrar.withMutation(of: self, keyPath: keyPath, mutation)
    }
}
The @ObservationTracked macro adds getters and setters to stored properties swift · at 18:16 ↗
@Observable final class Person {
    @ObservationTracked var name: String { get {  } set {  } }
    @ObservationTracked var age: Int { get {  } set {  } }
    @ObservationTracked var isFavorite: Bool { get {  } set {  } }
  
		internal let _$observationRegistrar = ObservationRegistrar<Person>()
    internal func access<Member>(
        keyPath: KeyPath<Person, Member>
    ) {
        _$observationRegistrar.access(self, keyPath: keyPath)
    }
    internal func withMutation<Member, T>(
        keyPath: KeyPath<Person, Member>,
        _ mutation: () throws -> T
    ) rethrows -> T {
        try _$observationRegistrar.withMutation(of: self, keyPath: keyPath, mutation)
    }
}
All that Swift code is folded away in the @Observable macro swift · at 18:33 ↗
@Observable final class Person {
    var name: String
    var age: Int
    var isFavorite: Bool
}
A wrapper for a file descriptor swift · at 23:59 ↗
struct FileDescriptor {
    private var fd: CInt
  
    init(descriptor: CInt) { self.fd = descriptor }

    func write(buffer: [UInt8]) throws {
        let written = buffer.withUnsafeBufferPointer {
            Darwin.write(fd, $0.baseAddress, $0.count)
        }
        // ...
    }
  
    func close() {
        Darwin.close(fd)
    }
}
The same FileDescriptor wrapper as a class swift · at 24:30 ↗
class FileDescriptor {
    private var fd: CInt
  
    init(descriptor: CInt) { self.fd = descriptor }

    func write(buffer: [UInt8]) throws {
        let written = buffer.withUnsafeBufferPointer {
            Darwin.write(fd, $0.baseAddress, $0.count)
        }
        // ...
    }
  
    func close() {
        Darwin.close(fd)
    }
    deinit {
        self.close(fd)
    }
}
Going back to the struct swift · at 25:05 ↗
struct FileDescriptor {
    private var fd: CInt
  
    init(descriptor: CInt) { self.fd = descriptor }

    func write(buffer: [UInt8]) throws {
        let written = buffer.withUnsafeBufferPointer {
            Darwin.write(fd, $0.baseAddress, $0.count)
        }
        // ...
    }
  
    func close() {
        Darwin.close(fd)
    }
}
Using Copyable in the FileDescriptor struct swift · at 26:06 ↗
struct FileDescriptor: ~Copyable {
    private var fd: CInt
  
    init(descriptor: CInt) { self.fd = descriptor }

    func write(buffer: [UInt8]) throws {
        let written = buffer.withUnsafeBufferPointer {
            Darwin.write(fd, $0.baseAddress, $0.count)
        }
        // ...
    }
  
    func close() {
        Darwin.close(fd)
    }
  
    deinit {
        Darwin.close(fd)
    }
}
`close()` can also be marked as consuming swift · at 26:35 ↗
struct FileDescriptor {
    private var fd: CInt
  
    init(descriptor: CInt) { self.fd = descriptor }

    func write(buffer: [UInt8]) throws {
        let written = buffer.withUnsafeBufferPointer {
            Darwin.write(fd, $0.baseAddress, $0.count)
        }
        // ...
    }
  
    consuming func close() {
        Darwin.close(fd)
    }
  
    deinit {
        Darwin.close(fd)
    }
}
When `close()` is called, it must be the final use swift · at 26:53 ↗
let file = FileDescriptor(fd: descriptor)
file.write(buffer: data)
file.close()
Compiler errors instead of runtime failures swift · at 27:20 ↗
let file = FileDescriptor(fd: descriptor)
file.close() // Compiler will indicate where the consuming use is
file.write(buffer: data) // Compiler error: 'file' used after consuming
Using C++ from Swift swift · at 28:52 ↗
// Person.h
struct Person {
    Person(const Person &);
    Person(Person &&);
    Person &operator=(const Person &);
    Person &operator=(Person &&);
    ~Person();
  
    std::string name;
    unsigned getAge() const;
};
std::vector<Person> everyone();

// Client.swift
func greetAdults() {
    for person in everyone().filter { $0.getAge() >= 18 } {
        print("Hello, \(person.name)!")
    }
}
Using Swift from C++ cpp · at 29:51 ↗
// Geometry.swift
struct LabeledPoint {
    var x = 0.0, y = 0.0
    var label: String = “origin”
    mutating func moveBy(x deltaX: Double, y deltaY: Double) { … }
    var magnitude: Double { … }
}

// C++ client
#include <Geometry-Swift.h>

void test() {
    Point origin = Point()
    Point unit = Point::init(1.0, 1.0, “unit”)
    unit.moveBy(2, -2)
    std::cout << unit.label << “ moved to “ << unit.magnitude() << std::endl;
}
An actor that manages a database connection swift · at 35:30 ↗
// Custom actor executors

actor MyConnection {
    private var database: UnsafeMutablePointer<sqlite3>
  
    init(filename: String) throws {  }
  
    func pruneOldEntries() {  }
    func fetchEntry<Entry>(named: String, type: Entry.Type) -> Entry? {  }
}

await connection.pruneOldEntries()
MyConnection with a serial dispatch queue and a custom executor swift · at 35:58 ↗
actor MyConnection {
  private var database: UnsafeMutablePointer<sqlite3>
  private let queue: DispatchSerialQueue
  
  nonisolated var unownedExecutor: UnownedSerialExecutor { queue.asUnownedSerialExecutor() }

  init(filename: String, queue: DispatchSerialQueue) throws {  }
  
  func pruneOldEntries() {  }
  func fetchEntry<Entry>(named: String, type: Entry.Type) -> Entry? {  }
}

await connection.pruneOldEntries()
Dispatch queues conform to SerialExecutor protocol swift · at 36:44 ↗
// Executor protocols

protocol Executor: AnyObject, Sendable {
    func enqueue(_ job: consuming ExecutorJob)
}

protocol SerialExecutor: Executor {
    func asUnownedSerialExecutor() -> UnownedSerialExecutor
    func isSameExclusiveExecutionContext(other executor: Self) -> Bool
}

extension DispatchSerialQueue: SerialExecutor {  }
C++ implementation of FoundationDB's "master data" actor cpp · at 39:22 ↗
// C++ implementation of FoundationDB’s “master data” actor

ACTOR Future<Void> getVersion(Reference<MasterData> self, GetCommitVersionRequest req) {
  	state std::map<UID, CommitProxyVersionReplies>::iterator proxyItr = self->lastCommitProxyVersionReplies.find(req.requestingProxy);
  	++self->getCommitVersionRequests;

  	if (proxyItr == self->lastCommitProxyVersionReplies.end()) {
      	req.reply.send(Never());
    	  return Void();
  	}
  	wait(proxyItr->second.latestRequestNum.whenAtLeast(req.requestNum - 1));
  
  	auto itr = proxyItr->second.replies.find(req.requestNum);
  	if (itr != proxyItr->second.replies.end()) {
    		req.reply.send(itr->second);
    		return Void();
  	}

  	// ...
}
Swift implementation of FoundationDB's "master data" actor swift · at 40:18 ↗
// Swift implementation of FoundationDB’s “master data” actor
func getVersion(
    myself: MasterData, req: GetCommitVersionRequest
) async -> GetCommitVersionReply? {
    myself.getCommitVersionRequests += 1

    guard let lastVersionReplies = lastCommitProxyVersionReplies[req.requestingProxy] else {
        return nil
    }

    // ...
    var latestRequestNum = try await lastVersionReplies.latestRequestNum
          .atLeast(VersionMetricHandle.ValueType(req.requestNum - UInt64(1)))

    if let lastReply = lastVersionReplies.replies[req.requestNum] {
        return lastReply
    }
}

Resources