Swift types can be categorized by how they handle copying, which in turn is decided by the type conformance to two marker protocols:

  • Copyable: is the ownership of the value type copied or transferred?
  • BitwiseCopyable: can the type be copied with a direct memory operation? (like memcpy)

A new syntax was added to explicitly indicate non-conformance to these protocols: a ~ prefix. Therefore, a type can also conform to ~Copyable (“noncopiable”) and ~BitwiseCopyable.

Noncopiable Types

A noncopyable type is a struct or enum where ownership can be transferred.

  • Using a noncopyable type after ownership has been transferred is a compiler error.
  • To mark a type as noncopyable, declare it with ~Copyable.
struct FileHandle: ~Copyable {
    let handle: Int32
    consuming func delete() {
    }
}

let handle = FileHandle(handle: 0)
let moved = handle      // transfers ownership
// print(handle.handle) // error: 'handle' no longer owns the struct
print(moved.handle)     // OK

Noncopiable types

  • can’t be global variables
  • can’t conform to any protocols except Sendable
  • can’t appear as properties of other than noncopyable types
  • don’t support generics at this time (thus, optionals and arrays of noncopyable are not allowed)
  • can have deinit, which run automatically at the end of the unique instance’s lifetime (unless explicitly suppressed with discard self in a consuming method)

Other limitations

  • Changing Copyable in a library will break the ABI.
  • Sometimes an error appears on the original definition of the type, not on the wrong use of it.
  • Actors and classes cannot be declared as noncopyable, but they can contain noncopyable stored properties.

Implementation detail: to implement noncopyable types Apple added Copyable conformance to all Swift types. When you conform to ~Copyable you are overriding this conformance.

The documentation says:

All existing first-class types (excluding nonescaping closures) implicitly satisfy this constraint.
In programming language theory, a first-class type or value generally refers to entities that can be:
  • Assigned to variables
  • Passed as arguments to functions
  • Returned from functions
  • Stored in data structures
In Swift, this would include:
  • Actors
  • Basic value types (Int, String, etc)
  • Classes
  • Enums and structs –unless they have noncopyable stored properties
  • Functions (except nonescaping closures)
  • Protocols
I haven’t been able to find a non Copyable language type so I guess all are.

Function signature

Noncopyable parameters can be

  • borrowing: read only access
  • inout: mutation allowed
  • consuming: caller can’t use it after calling the function
  • Example

struct FileHandle: ~Copyable {
    var handle: Int32
}

// Borrowing (read-only access)
func inspect(file: borrowing FileHandle) {
    print("handle: \(file.handle)")
    // error: cannot assign to property: 'file' is a 'let' constant
    // file.handle = 1
}

// Inout (mutation allowed)
func modify(file: inout FileHandle) {
    file.handle = 1
}

// Consuming (takes ownership, invalidates original)
func close(file: consuming FileHandle) {
    file.handle = 1
    print(file.handle)
    switch file.handle {
        default: ()
    }
    print(file.handle)
    // caller won’t be able to use the file after this
}

var file = FileHandle(handle: 42)

inspect(file: file)    // Can still use file after this
modify(file: &file)    // Can still use file after this
close(file: file)      // Cannot use file after this
// inspect(file: file) // Error: 'file' used after consume

Methods of noncopyable types:

  • mutating methods can change the instance
  • non mutating methods can be
    • borrowing (default): allow read access
    • consuming: invalidate the instance once they complete
    • Example
struct FileHandle: ~Copyable {
    var handle: Int32
    
    // Mutating method - can change the instance
    mutating func updateHandle(to newHandle: Int32) {
        handle = newHandle
    }
    
    // Borrowing method (default) - only reads
    func readHandle() -> Int32 {
        handle
    }
    
    // Consuming method - invalidates the instance
    consuming func close() {
        print(handle)
        discard self // prevents deinit from running
    }
    
    deinit {
        // won’t run if you call close()
        print("deinit")
    }
}

var file = FileHandle(handle: 42)

_ = file.readHandle()       // Can still use file after this
file.updateHandle(to: 100)  // Can still use file after this
file.close()                // Cannot use file after this
// file.readHandle()        // Error: file was consumed

Pattern Matching

0432 refined the behavior of noncopyable structs and enums presented in 0390. I’m using switch in examples, but the following applies to pattern matching performed with switch, if, while, for, and guard.

Pattern matching a noncopyable can either borrow or consume:

  • Temporary values (like a function result) are consumed.
  • Variables and stored properties are borrowed, but you may consume them adding consume.
  • Example

Example of switch borrow/consume

Variables and stored properties are borrowed by default.

var handle = FileHandle(handle: 42)

switch handle {  // borrows by default
case let file:   // borrowing binding
    print(file.handle)
} 
// can still use handle after this
print(handle.handle)

But you may consume them if you add consume.

var handle = FileHandle(handle: 42)

switch consume handle {  // consuming switch
case let file:           // consuming binding
    print(file.handle)
} // handle is invalid after this
// print(handle.handle) // error: 'handle' used after consume

Temporary values (like a function result) are consumed by default.

func createHandle() -> FileHandle {
    FileHandle(handle: 42)
}

switch createHandle() { // consumes by default
case let file:
    print(file.handle)
}

Restrictions when switching over noncopyable types:

  • The switch cannot consume the value during pattern matching
  • where clauses cannot consume pattern bindings
  • The value’s ownership can only be transferred once the final match is chosen
  • Example

Example of switch restrictions

func f() {
    struct FileHandle: ~Copyable {
        let handle: Int32
        func isReady() -> Bool { true }
        consuming func close() -> Bool { true }
    }
    
    // warning: variable 'handle' was never mutated
    var handle = FileHandle(handle: 42)
    
    // 'where' clause can borrow, but not consume.
    switch consume handle {
        case let file where file.isReady():
            _ = file.close()
        
        // case let file where file.close(): // not allowed!
        //     print("closed")
        
        default: ()
    }
}

f()

Two things:

  • There is a false warning on handle: it needs to be a var in order to compile.
  • I’m wrapping the example on a function because global noncopyable are not allowed.

Bitwise Copyable

A bitwise-copyable type is one that can be safely copied using direct memory operations (like memcpy), and requires no special cleanup when destroyed.

Examples of bitwise-copyable types are pointers, ints, floats, structs with bitwise-copyable fields. See the whole list in SE-0426 Appendix: Standard library conformers.

To mark a type as bitwise-copyable, conform it to the empty BitwiseCopyable protocol. The only condition is that all stored properties must be BitwiseCopyable. Example.

Example of BitwiseCopyable types

struct Foo: BitwiseCopyable { // OK
    /* empty */ 
}
struct Foo: BitwiseCopyable { // OK
    let x: Int // Int is BitwiseCopyable
    var string: String { "\(x)" } // computed
}

struct Foo: BitwiseCopyable { // error
    let x: Int
    let string: String // stored and NOT BitwiseCopyable
}

Automatic conformance to BitwiseCopyable is inferred

  • for public enums that are @frozen
  • for non public types when:
    • All stored properties are bitwise-copyable
    • The type is defined within the current module
    • The type is not marked with ~BitwiseCopyable
    • Example

// an exported type is automatically BitwiseCopyable
struct LocalPoint {
    let x: Int
    let y: Int
}

// Explicitly not BitwiseCopyable
struct NonBitwisePoint: ~BitwiseCopyable {
    let x: Int
    let y: Int
}

// an exported type must be explicitly marked
public struct PublicPoint: BitwiseCopyable {
    public let x: Int
    public let y: Int
}

Generics types can be conditionally bitwise-copyable. Example


struct Container<T> {
    var value: T
}

// container is BitwiseCopyable only when T is
extension Container: BitwiseCopyable where T: BitwiseCopyable {}

let intContainer = Container(value: 42)       // BitwiseCopyable
let stringContainer = Container(value: "hi")  // Not BitwiseCopyable

Limitations of bitwise-copyable types:

  • Cannot be applied to global variables
  • Cannot extend the protocol
  • Cannot be used with dynamic type casting (as?, is)
  • Example

Examples of BitwiseCopyable limitations

// error: cannot extend protocol 'BitwiseCopyable'
extension BitwiseCopyable {
    func copy() -> Self { self }
}

// error: marker protocol 'BitwiseCopyable' cannot be used in conditional cast
struct Point: BitwiseCopyable {
    var x: Int
    var y: Int
}
let point = Point(x: 0, y: 0)
let isBitwiseCopyable = point is BitwiseCopyable   // error
let asBitwiseCopyable = point as? BitwiseCopyable  // error

// Error: conditional conformance to non-marker protocol 'MyProtocol' 
// cannot depend on conformance of 'Element' to marker protocol 'BitwiseCopyable'
protocol MyProtocol {}
extension Array: MyProtocol where Element: BitwiseCopyable {}

ABI compatibility of a library will break (you will have to recompile its clients) if you add and then remove BitwiseCopyable. Same as with ~Copyable. Example.

Not sure if the type will remain bitwise-copyable? better opt-out to preserve ABI compatibility.

// Opting out an otherwise BitwiseCopyable type
// because you plan to add non BitwiseCopyable properties
struct Coordinate4: ~BitwiseCopyable {
    var x, y, z, w: Double
}

References

For more details, see