2024 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 ↗Chapters
Code shown on screen · 17 snippets
Player as a struct
struct Player {
var icon: String
}
func test() {
let player1 = Player(icon: "🐸")
var player2 = player1
player2.icon = "🚚"
assert(player1.icon == "🐸")
} Player as a class
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
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
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
struct FloppyDisk: ~Copyable {}
func copyFloppy() {
let system = FloppyDisk()
let backup = consume system
load(system)
// ...
}
func load(_ disk: borrowing FloppyDisk) {} Missing ownership for FloppyDisk
struct FloppyDisk: ~Copyable { }
func newDisk() -> FloppyDisk {
let result = FloppyDisk()
format(result)
return result
}
func format(_ disk: FloppyDisk) {
// ...
} Consuming ownership
struct FloppyDisk: ~Copyable { }
func newDisk() -> FloppyDisk {
let result = FloppyDisk()
format(result)
return result
}
func format(_ disk: consuming FloppyDisk) {
// ...
} Borrowing ownership
struct FloppyDisk: ~Copyable { }
func newDisk() -> FloppyDisk {
let result = FloppyDisk()
format(result)
return result
}
func format(_ disk: borrowing FloppyDisk) {
var tempDisk = disk
// ...
} Inout ownership
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
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
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
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
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
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
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
protocol Cancellable {
mutating func cancel()
}
extension Job: Cancellable {
mutating func cancel() {
action = nil
}
} Cancellable for all Jobs
protocol Cancellable: ~Copyable {
mutating func cancel()
}
extension Job: Cancellable where Action: ~Copyable {
mutating func cancel() {
action = nil
}
} Resources
Related sessions
-
41 min