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

2024 Swift

WWDC24 · 22 min · Swift

Consume noncopyable types in Swift

Get started with noncopyable types in Swift. Discover what copying means in Swift, when you might want to use a noncopyable type, and how value ownership lets you state your intentions clearly.

Watch at developer.apple.com ↗

Transcript all transcripts

Chapters

Code shown on screen · 17 snippets

Player as a struct swift · at 0:52 ↗
struct Player {
  var icon: String
}

func test() {
  let player1 = Player(icon: "🐸")
  var player2 = player1
  player2.icon = "🚚"
  assert(player1.icon == "🐸")
}
Player as a class swift · at 1:55 ↗
class PlayerClass {
  var icon: String
  init(_ icon: String) { self.icon = icon }
}

func test() {
  let player1 = PlayerClass("🐸")
  let player2 = player1
  player2.icon = "🚚"
  assert(player1.icon == "🐸")
}
Deeply copying a PlayerClass swift · at 3:00 ↗
class PlayerClass {
  var data: Icon
  init(_ icon: String) { self.data = Icon(icon) }

  init(from other: PlayerClass) {
    self.data = Icon(from: other.data)
  } 
}

func test() {
  let player1 = PlayerClass("🐸")
  var player2 = player1
  player2 = PlayerClass(from: player2)
  player2.data.icon = "🚚"
  assert(player1.data.icon == "🐸")
}

struct Icon {
  var icon: String
  init(_ icon: String) { self.icon = icon }
  init(from other: Icon) { self.icon = other.icon }
}
Copyable BankTransfer swift · at 5:10 ↗
class BankTransfer {
  var complete = false

  func run() {
    assert(!complete)
    // .. do it ..
    complete = true
  }

  deinit {
    if !complete { cancel() }
  }

  func cancel() { /* ... */ }
}

func schedule(_ transfer: BankTransfer,
              _ delay: Duration) async throws {

  if delay < .seconds(1) {
    transfer.run()
  }

  try await Task.sleep(for: delay)
  transfer.run()
}

func startPayment() async {
  let payment = BankTransfer()
  log.append(payment)
  try? await schedule(payment, .seconds(3))
}

let log = Log()

final class Log: Sendable {
  func append(_ transfer: BankTransfer) { /* ... */ }
}
Copying FloppyDisk swift · at 7:46 ↗
struct FloppyDisk: ~Copyable {}

func copyFloppy() {
  let system = FloppyDisk()
  let backup = consume system
  load(system)
  // ...
}

func load(_ disk: borrowing FloppyDisk) {}
Missing ownership for FloppyDisk swift · at 8:18 ↗
struct FloppyDisk: ~Copyable { }

func newDisk() -> FloppyDisk {
  let result = FloppyDisk()
  format(result)
  return result
}

func format(_ disk: FloppyDisk) {
  // ...
}
Consuming ownership swift · at 9:00 ↗
struct FloppyDisk: ~Copyable { }

func newDisk() -> FloppyDisk {
  let result = FloppyDisk()
  format(result)
  return result
}

func format(_ disk: consuming FloppyDisk) {
  // ...
}
Borrowing ownership swift · at 9:26 ↗
struct FloppyDisk: ~Copyable { }

func newDisk() -> FloppyDisk {
  let result = FloppyDisk()
  format(result)
  return result
}

func format(_ disk: borrowing FloppyDisk) {
  var tempDisk = disk
  // ...
}
Inout ownership swift · at 9:55 ↗
struct FloppyDisk: ~Copyable { }

func newDisk() -> FloppyDisk {
  var result = FloppyDisk()
  format(&result)
  return result
}

func format(_ disk: inout FloppyDisk) {
  var tempDisk = disk
  // ...
  disk = tempDisk
}
Noncopyable BankTransfer swift · at 10:28 ↗
struct BankTransfer: ~Copyable {
  consuming func run() {
    // .. do it ..
    discard self
  }

  deinit {
    cancel()
  }

  consuming func cancel() {
    // .. do the cancellation ..
    discard self
  }
}
Schedule function for noncopyable BankTransfer swift · at 11:10 ↗
func schedule(_ transfer: consuming BankTransfer,
              _ delay: Duration) async throws {

  if delay < .seconds(1) {
    transfer.run()
    return
  }

  try await Task.sleep(for: delay)
  transfer.run()
}
Overview of conformance constraints swift · at 12:12 ↗
struct Command { }

protocol Runnable {
  consuming func run()
}

extension Command: Runnable {
  func run() { /* ... */ }
}

func execute1<T>(_ t: T) {}

func execute2<T>(_ t: T) 
  where T: Runnable {
  t.run()
}

func test(_ cmd: Command, _ str: String) {
  execute1(cmd)
  execute1(str)

  execute2(cmd)
  execute2(str) // expected error: 'execute2' requires that 'String' conform to 'Runnable'
}
Noncopyable generics: 'execute' function swift · at 15:50 ↗
protocol Runnable: ~Copyable {
  consuming func run()
}

struct Command: Runnable {
  func run() { /* ... */ }
}

struct BankTransfer: ~Copyable, Runnable {
  consuming func run() { /* ... */ }
}

func execute2<T>(_ t: T)
  where T: Runnable {
  t.run()
}

func execute3<T>(_ t: consuming T)
  where T: Runnable,
        T: ~Copyable {
  t.run()
}

func test() {
  execute2(Command())
  execute2(BankTransfer()) // expected error: 'execute2' requires that 'BankTransfer' conform to 'Copyable'

  execute3(Command())
  execute3(BankTransfer())
}
Conditionally Copyable swift · at 18:05 ↗
struct Job<Action: Runnable & ~Copyable>: ~Copyable {
  var action: Action?
}

func runEndlessly(_ job: consuming Job<Command>) {
  while true {
    let current = copy job
    current.action?.run()
  }
}

extension Job: Copyable where Action: Copyable {}

protocol Runnable: ~Copyable {
  consuming func run()
}

struct Command: Runnable {
  func run() { /* ... */ }
}
Extensions of types with noncopyable generic parameters swift · at 19:27 ↗
extension Job {
  func getAction() -> Action? {
    return action
  }
}

func inspectCmd(_ cmdJob: Job<Command>) {
  let _ = cmdJob.getAction()
  let _ = cmdJob.getAction()
}

func inspectXfer(_ transferJob: borrowing Job<BankTransfer>) {
  let _ = transferJob.getAction() // expected error: method 'getAction' requires that 'BankTransfer' conform to 'Copyable'
}


struct Job<Action: Runnable & ~Copyable>: ~Copyable {
  var action: Action?
}

extension Job: Copyable where Action: Copyable {}

protocol Runnable: ~Copyable {
  consuming func run()
}

struct Command: Runnable {
  func run() { /* ... */ }
}

struct BankTransfer: ~Copyable, Runnable {
  consuming func run() { /* ... */ }
}
Cancellable for Jobs with Copyable actions swift · at 20:14 ↗
protocol Cancellable {
  mutating func cancel()
}

extension Job: Cancellable {
  mutating func cancel() {
    action = nil
  }
}
Cancellable for all Jobs swift · at 21:00 ↗
protocol Cancellable: ~Copyable {
  mutating func cancel()
}

extension Job: Cancellable where Action: ~Copyable {
  mutating func cancel() {
    action = nil
  }
}

Resources