Copying
By default, when you copy a variable, you’re copying its contents
For value types, the contents are the actual data that makes up the instance
struct Player { ... }
let player1 = Player(icon: "🐸")
var player2 = player1
player2.icon = "🚚" // player1.icon is still "🐸"For reference types, the contents are a managed reference, so the reference is copied, not the object itself
Shallow copy by default
class PlayerClass { ... }
let player1 = PlayerClass("🐸")
let player2 = player1
player2.icon = "🚚" // player1.icon is "🚚"You can make reference types perform an explicit deep copy by defining a custom initializer
Does not control whether Swift can make automatic copies
Copyable
Copyableis a marker protocol, likeSendablei.e. it doesn’t have any associated requirements
Describes the ability for a type to be automatically copied by Swift
Everything is
Copyablein Swift by defaultTypes, generic parameters, protocols and associated types, boxed protocol types, etc.
This is an assumption, since
Copyabletypes are generally easier to work with
Noncopyable types
You can suppress the default
Copyablebehavior by annotating your types with~Copyable:
struct FloppyDisk: ~Copyable { ... }When copying is not supported, Swift will consume the variable instead
Can optionally annotate variable consumption using the
consumekeyword explicitlyReading consumed values after they’ve been consumed is a compile-time error
let system = FloppyDisk()
print(system) // this works
let backup = system // can also be written as `consume system`
print(system) // this produces an error: system is used after consumeOwnership
With
Copyabletypes by default, you don’t have to worry about ownershipFunctions you wrote would effectively receive a copy
With
~Copyable, you have to declare what ownership your functions have over the~Copyablevalues
There are 3 kinds of ownership, outlined below.
Consuming
Your function will “take” the argument from the caller, and will effectively own it
You can mutate the argument
Caller has no access to the value anymore
func format(_ disk: consuming FloppyDisk) { ... }
let result = FloppyDisk()
format(disk)
return result // produces an error: result is consumed more than onceBorrowing
Gives you temporary read-only access to the argument
Similar to how parameters already work by default for
CopyabletypesCannot consume, or mutate, an explicitly borrowed argument
func format(_ disk: borrowing FloppyDisk) {
var tempDisk = disk // produces an error: disk is borrowed and cannot be consumed
}Mutating, or inout
Provides temporary write access to a caller-owned variable
Can consume the parameter
Have to reinitialize the parameter at some point before end of function scope
func format(_ disk: inout FloppyDisk) {
var tempDisk = disk
// Have to reinitialize the parameter before end of scope:
disk = tempDisk
}Consumable resources
Can mark functions as
consumingto take the value forselfaway from callersGuarantees function cannot be called more than once on the same instance
Relationship to
consumingparameters is similar to howmutatingis used to indicate aninoutreference toself
struct BankTransfer: ~Copyable {
consuming func run() {
// Never called more than once for the same BankTransfer.
}
}By default, reaching the end of the
consumingfunction scope will destroy the instance (and calldeinit)Can call
discard selfat end of function scope to destroy without callingdeinit
consuming func run() {
...
// Destroy `self` without calling `deinit`:
discard self
}Generics
Core idea: conformance constraints describe generic types
AnyisCopyableSwift 6 introduces noncopyable generics
Noncopyable generics
Recall: by default, every protocol inherits from
CopyableYou can now remove the
Copyableconstraint from a protocol explicitly:
protocol Runnable: ~Copyable {
consuming func run()
}Generics also have a default
CopyableconstraintYou can also remove the
Copyablerequirement from a generic constraint:
/// This requires T to be *both* `Runnable` and `Copyable`
func execute<T>(_ t: consuming T) where T: Runnable { ... }
/// This requires T to be `Runnable`, but *not necessarily* `Copyable`
func execute<T>(_ t: consuming T) where T: Runnable, T: ~Copyable { ... }Key point: A regular constraint is more specific and narrows the set of permitted types. A tilde constraint is less specific and broadens the types.
Nesting ~Copyable values
May store
~Copyablevalues inside a class, since copying only copies a referenceOr, containing type must be
~Copyableitself
Conditional Copyable
You may define conditional
Copyableconformance on a~Copyabletype using an extensionSince
Copyableis a marker, no additional declarations are necessary inside such an extension
/// Stores a *potentially* `~Copyable` value, so must be a class or suppress `Copyable`
struct Job<Action: Runnable & ~Copyable>: ~Copyable {
var action: Action?
}
/// Whenever the contained `Action` is `Copyable`, mark `Job` as `Copyable` too:
extension Job: Copyable where Action: Copyable { }Extensions
By default, generic parameters in scope of the extended type are constrained to
CopyableIncludes
Selfin a protocol
struct Job<Action: Runnable & ~Copyable>: ~Copyable { }
extension Job { ... }
// By default, equivalent to:
extension Job where Action: Copyable { ... }