Noncopyable types in Swift

In Swift, types are copyable by default. This design simplifies development by allowing values to be easily duplicated when assigned to new variables or passed to functions. While convenient, this behavior can sometimes lead to unintended issues. For instance, copying a single-use ticket or duplicating a database connection could result in invalid states or resource conflicts.

To address these challenges, Swift 5.9 introduced noncopyable types. By marking a type as ~Copyable, we explicitly prevent Swift from duplicating it. This guarantees unique ownership of the value and enforces stricter constraints, reducing the risk of errors.

Here’s a simple example of a noncopyable type:

struct SingleUseTicket: ~Copyable {
    let ticketID: String
}

In contrast to the regular behavior of value types, when we assign an instance of a noncopyable type to a new variable, the value gets moved instead of being copied. If we attempt to use the original variable later, we'll get a compile-time error.

let originalTicket = SingleUseTicket(ticketID: "S645")
let newTicket = originalTicket

// Error: 'originalTicket' used after consume
// print(originalTicket.ticketID)

Classes can't be declared noncopyable. All class types remain copyable by retaining and releasing references to the object.

# Methods in noncopyable types

Methods in noncopyable types can read, mutate, or consume self.

# Borrowing methods

Methods inside a noncopyable type are borrowing by default. This means they only have read access to the instance, allowing safe inspection of the instance without affecting its validity.

struct SingleUseTicket: ~Copyable {
    let ticketID: String
    
    func describe() {
        print("This ticket is \(ticketID).")
    }
}

let ticket = SingleUseTicket(ticketID: "A123")

// Prints `This ticket is A123.`
ticket.describe()

# Mutating methods

A mutating method provides temporary write access to self, allowing modifications without invalidating the instance.

struct SingleUseTicket: ~Copyable {
    var ticketID: String

    mutating func updateID(newID: String) {
        ticketID = newID
        print("Ticket ID updated to \(ticketID).")
    }
}

var ticket = SingleUseTicket(ticketID: "A123")

// Prints `Ticket ID updated to B456.`
ticket.updateID(newID: "B456")

# Consuming methods

A consuming method takes ownership of self, invalidating the instance once the method completes. This is useful for tasks that finalize or dispose of a resource. After the method is called, any attempt to access the instance results in a compile-time error.

struct SingleUseTicket: ~Copyable {
    let ticketID: String
    
    consuming func use() {
        print("Ticket \(ticketID) used.")
    }
}

func useTicket() {
    let ticket = SingleUseTicket(ticketID: "A123")
    ticket.use()
    
    // Error: 'ticket' consumed more than once
    // ticket.use()
}

useTicket()

Note that we can't consume noncopyable types stored in global variables, that's why we wrapped the code into the useTicket() function in our example.

# Noncopyable types in function arguments

When passing noncopyable types as arguments to functions, Swift requires us to specify the ownership model for that function. We can mark parameters as borrowing, inout, or consuming, each offering different levels of access, similar to methods inside the types.

# Borrowing parameters

Borrowing ownership allows the function to temporarily read the value without consuming or mutating it.

func inspectTicket(_ ticket: borrowing SingleUseTicket) {
    print("Inspecting ticket \(ticket.ticketID).")
}

# Inout parameters

The inout modifier provides temporary write access to a value, allowing the function to modify it while returning ownership to the caller.

func updateTicketID(_ ticket: inout SingleUseTicket, to newID: String) {
    ticket.ticketID = newID
    print("Ticket ID updated to \(ticket.ticketID).")
}

# Consuming parameters

When a parameter is marked as consuming, the function takes full ownership of the value, invalidating it for the caller. This is ideal for tasks where the value is no longer needed after the function.

func processTicket(_ ticket: consuming SingleUseTicket) {
    ticket.use()
}

# Deinitializers and the discard operator

Noncopyable structs and enums can have deinitializers, like classes, which run automatically at the end of the instance's lifetime.

struct SingleUseTicket: ~Copyable {
    let ticketID: Int
    
    deinit {
        print("Ticket deinitialized.")
        
        // cleanup logic
    }
}

However, when both a consuming method and a deinitializer perform cleanup, there is a risk of redundant operations. To address this, Swift introduced the discard operator.

By using discard self in a consuming method, we explicitly stop the deinitializer from being called, avoiding duplicate logic:

struct SingleUseTicket: ~Copyable {
    let ticketID: Int
    
    consuming func invalidate() {
        print("Ticket \(ticketID) invalidated.")
        
        // cleanup logic
        
        discard self
    }
    
    deinit {
        print("Ticket deinitialized.")
        
        // cleanup logic
    }
}

Note that we can only use discard if our type contains trivially-destroyed stored properties. It can't have reference-counted, generic, or existential fields.


Noncopyable types are invaluable in scenarios where unique ownership is essential. They prevent duplication of resources like single-use tokens, cryptographic keys, or database connections, reducing the risk of errors. By enforcing ownership rules at compile time, Swift enables developers to write safer, more efficient code.

While noncopyable types might not be required in every project, they provide a powerful tool for ensuring safety and clarity in critical systems. As Swift continues to evolve, these types represent a significant step forward in the language’s focus on performance and correctness.


As someone who has worked extensively with Swift, I've gathered many insights over the years. I've compiled them in my book Swift Gems, which is packed with advanced tips and techniques to help intermediate and advanced Swift developers enhance their coding skills. From optimizing collections and handling strings to mastering asynchronous programming and debugging, "Swift Gems" provides practical advice to elevate your Swift development. Grab your copy of Swift Gems and let's explore the advanced techniques together.

Swift Gems by Natalia Panferova book coverSwift Gems by Natalia Panferova book cover

Level up your Swift skills!$35

100+ tips to take your Swift code to the next level

Swift Gemsby Natalia Panferova

  • Advanced Swift techniques for experienced developers bypassing basic tutorials
  • Curated, actionable tips ready for immediate integration into any Swift project
  • Strategies to improve code quality, structure, and performance across all platforms

Level up your Swift skills!

100+ tips to take your Swift code to the next level

Swift Gems by Natalia Panferova book coverSwift Gems by Natalia Panferova book cover

Swift Gems

by Natalia Panferova

$35