Swift Foundation Rust

Swift Meets Rust: Understanding Noncopyable Types and Ownership Systems

Swift 5.9 just introduced something that made Rust developers chuckle - noncopyable types. It’s like Swift finally admitted that Rust had the right idea all along about controlling how values get passed around in code.

Let me show you what I mean. In regular Swift, values get copied around like free candy at a parade. You create a database connection, assign it somewhere else, and boom - you’ve got two connections when you only wanted one:

Basic Concepts: The Foundation

Value vs Reference Types in Swift

Before we dive into noncopyable types, let’s understand how Swift normally handles values. In Swift, we have two fundamental ways objects behave when we pass them around.

Value types, which include structs and enums, work like photographs. When you share a photo with someone, you’re giving them their own copy. If they draw on their copy, your original stays unchanged. This is exactly how Swift structs behave by default – when you pass them around, each piece of code gets its own copy to work with.

Reference types, like classes and actors, work more like sharing a Google Doc link. When you share the link, everyone is looking at the exact same document. If someone makes changes, everyone sees those changes because they’re all referencing the same thing.

The Copying Problem

Now, imagine you’re managing access to a secure database. With regular Swift structs, you can run into a subtle but serious problem:

struct DatabaseConnection {
    let connectionString: String
}

let primaryConnection = DatabaseConnection(connectionString: "mysql://main")
let backupConnection = primaryConnection  // Oops! Now we have two connections!

This innocent-looking code actually creates two database connections when we only wanted one. It’s like accidentally making a copy of your house key – suddenly there are two keys when there should only be one. In real applications, this can waste resources, create security vulnerabilities, exhaust connection pools, and cause unpredictable behavior when your system thinks it’s dealing with one connection but actually has multiple copies floating around.

Introducing ~Copyable: The Solution

Swift 5.9 introduced a solution to this problem with the ~Copyable attribute. Think of it like creating a special key that physically cannot be duplicated:

struct DatabaseConnection: ~Copyable {
    let connectionString: String
}

let primaryConnection = DatabaseConnection(connectionString: "mysql://main")
let backupConnection = primaryConnection  // This MOVES the connection instead of copying
// primaryConnection is no longer usable here

When you mark something as ~Copyable, you’re telling Swift that this type represents something that should only exist in one place at a time. Instead of copying these values when you pass them around, Swift moves them. Once you give a noncopyable value to something else, you can’t use it anymore – just like how you can’t use your house key after you’ve given it to someone else.

This feature is perfect for representing real-world resources that shouldn’t be duplicated. Database connections, file handles, authentication tokens, network sockets, and hardware device controls are all examples of things that should exist in only one place at a time. The compiler helps enforce this rule, preventing accidental duplication and ensuring these important resources are handled correctly.

The Origins and Implementation of Swift’s Noncopyable Types

While Swift’s noncopyable types might be new, the concept itself has been proven in other systems programming languages, particularly Rust. The idea of enforcing unique ownership of resources through the compiler has shown itself to be powerful and effective.

Let’s look at how Swift implements this concept. When you create and move a noncopyable value:

struct DatabaseConnection: ~Copyable {
    let connectionString: String
}

let db1 = DatabaseConnection(connectionString: "mysql://...")
let db2 = db1 // This is a move, not a copy

The magic happens in that second line. Instead of copying the database connection like Swift would normally do with structs, it performs a direct memory transfer. There’s no behind-the-scenes reference counting or copy-on-write behavior – the value literally moves from one location to another, and the original location is marked as consumed.

Think of it like transferring money between bank accounts. When you move money from your savings to your checking account, the money doesn’t get copied – it moves. After the transfer, that specific money is no longer in your savings account. Swift’s noncopyable types work the same way: once moved, the original is no longer available.

This approach was heavily influenced by Rust’s ownership system, which has been using similar principles for years. In Rust, you’d write:

struct DatabaseConnection {
    connection_string: String
}

let db1 = DatabaseConnection { connection_string: String::from("mysql://...") };
let db2 = db1; // db1 is now invalid

While the syntax differs, the underlying principle is the same: ensuring that resources can only exist in one place at a time. This pattern has proven especially valuable for managing critical resources that shouldn’t be duplicated, such as database connections, file handles, or network sockets.

Swift’s Noncopyable Types: Keyword Guide

Let me break down all the important keywords and how they work together in Swift’s noncopyable system:

Core Keywords

~Copyable

  • What: The main attribute that marks a type as noncopyable
  • Where: Used on struct or enum declarations
  • Example:
struct Resource: ~Copyable {
    let id: String
}

consuming

Used in three key places:

  1. Method declarations:
consuming func close() {
    // Method that consumes self
}
  1. Function parameters:
func process(_ resource: consuming Resource)
  1. Collection operations:
array.append(consuming: resource)

borrowing

  • Used for read-only access
  • Common in method and function parameters
func inspect(_ resource: borrowing Resource) {
    print(resource.id) // Can only read
}

inout

  • For temporary mutable access
  • The value must be returned to the caller
func update(_ resource: inout Resource) {
    resource.id = "new" // Can modify
}

discard

  • Tells Swift to skip the deinitializer
  • Used inside consuming methods
consuming func cleanup() {
    // Do cleanup
    discard self // Skip deinit
}

Passing Around Values: Swift’s Three Flavors of Sharing

Let me explain how Swift handles passing these noncopyable values around. This is where Swift actually gets more flexible than Rust, offering three distinct ways to share values with functions.

First, you can fully hand over ownership using consuming parameters:

func processConnection(_ conn: consuming DatabaseConnection) {
    // This function takes full ownership of the connection
    // Once it's done, the connection is gone for good
}

Think of this like giving someone your house key permanently. Once you’ve handed it over, you can’t use it anymore. This is perfect for when a function needs to take complete control of a resource, like closing a file or shutting down a connection.

Then there’s “borrowing,” which is like letting someone look at your key but not take it:

func inspectConnection(_ conn: borrowing DatabaseConnection) {
    // Can look at the connection details
    print(conn.connectionString)
    // But can't modify or consume it
}

Finally, there’s inout parameters, which let functions temporarily take control and modify something:

func updateConnection(_ conn: inout DatabaseConnection) {
    // Can modify the connection
    // When done, ownership returns to the caller
}

This is like letting someone borrow your key, make changes to it, and then give it back. They can’t keep it or destroy it - they have to return it.

Here’s how it looks in practice:

let conn = DatabaseConnection(connectionString: "mysql://...")

// Let's try these different approaches
inspectConnection(conn)        // Just looking, conn is still yours
updateConnection(&conn)        // Temporarily lend conn for modifications
processConnection(conn)        // conn is gone forever now
// inspectConnection(conn)     // Error! conn no longer exists

The compiler keeps track of all this, making sure you can’t accidentally use a connection after you’ve given it away. It’s like having a really attentive assistant who stops you from trying to use a key you’ve already given to someone else.

Here’s a neat trick that even Rust doesn’t have - Swift lets you explicitly tell the compiler “I’m done with this value” using the discard operator:

struct Connection: ~Copyable {
    let id: String
    
    consuming func close() {
        // cleanup code
        discard self // Tell Swift "I handled this, don't run deinit"
    }
    
    deinit {
        // This won't run if we used discard
    }
}

Collections and Noncopyable Types: Managing Groups of Unique Things

Here’s where things get really interesting. Think about managing a pool of database connections - you need to store multiple connections, but each one still needs to maintain its unique ownership rules. Let me show you how Swift and Rust handle this differently.

In Rust, collections handle non-copyable types quite naturally:

let mut connections = Vec::new();
connections.push(DatabaseConnection { ... });
let first_conn = connections.remove(0); // Clean ownership transfer

Rust treats this like a box of unique keys - you can add keys to the box or take them out, and the ownership rules follow naturally.

Swift needed to add some special sauce to make this work:

struct Connection: ~Copyable {
    let id: String
}

var connections: [Connection] = []
let newConn = Connection(id: "1")
connections.append(consuming: newConn) // Notice the 'consuming' keyword

That consuming keyword is important - it tells Swift “I’m giving this connection to the array permanently.” It’s like telling the array “Here, you’re responsible for this now.”

Taking things out is also special:

if let conn = connections.take(at: 0) {
    // We now own this connection
    // The array no longer has it
}

Instead of the regular array methods like remove(at:), Swift gives us take(at:) for noncopyable types. This explicitly shows we’re transferring ownership out of the array.

This becomes super useful in real-world scenarios. Imagine managing a pool of database connections:

struct ConnectionPool {
    private var connections: [Connection] = []
    
    mutating func checkout() throws -> Connection {
        if let conn = connections.take(at: 0) {
            return conn
        }
        throw PoolError.empty
    }
    
    mutating func return(_ conn: consuming Connection) {
        connections.append(consuming: conn)
    }
}

Here we’ve built a simple connection pool that maintains ownership rules - you can check out a connection (taking ownership) and return it (giving ownership back to the pool). The compiler ensures no connection can be used twice or lost in the process.

When Optionals Enter the Chat

Swift’s optional types are like boxes that might contain a value - or might be empty. When we mix these with noncopyable types, we get into interesting territory. Think about it like having a special delivery box that can only be used once. Once you take what’s inside, the box itself becomes unusable.

struct DatabaseConnection: ~Copyable {
    let connectionString: String
}

var maybeConnection: DatabaseConnection? = DatabaseConnection(connectionString: "mysql://...")
let actualConnection = maybeConnection // The optional and its contents are moved
// maybeConnection is now nil and unusable

This is quite different from how regular Swift optionals work. Normally, when you take a value from an optional, the original optional keeps its value. But with noncopyable types, the act of taking the value out consumes both the value AND the optional container. It’s like the delivery box disintegrates after you take out your package! This behavior helps prevent subtle bugs where you might accidentally try to use the same connection twice through its optional wrapper. The compiler enforces that once you’ve moved the value out, there’s no way to accidentally access it again through the original optional.

Generic Containers: Making Things Flexible

When we want to create containers that can hold any noncopyable type, we enter the world of generics. This is where Swift and Rust take different approaches to the same problem.

In Swift, we explicitly mark our generic type as noncopyable:

struct Container<T: ~Copyable> {
    var value: T
    
    consuming func extractValue() -> T {
        let v = value
        discard self  // We're done with the container after extraction
        return v
    }
}

This is like creating a special box that can hold any unique item - but only unique items. The ~Copyable constraint is Swift’s way of saying “this box only works with things that can’t be copied.”

Rust’s approach is more implicit but equally powerful:

struct Container<T> {
    value: T
}

impl<T> Container<T> {
    fn extract_value(self) -> T {
        self.value  // Rust figures out the move semantics automatically
    }
}

Rust doesn’t need special markers because its ownership system is baked into every type. It’s like Rust containers already know how to handle both copyable and non-copyable things.

Error Handling and Cleanup: When Things Go Wrong

This is where Swift really shows its thoughtful design. Let’s talk about what happens when errors occur while you’re handling unique resources. It’s like juggling valuable items - what happens if you need to catch them or hand them off safely?

The Cleanup Challenge

Here’s a common scenario - you have a file handle that needs to be properly closed, even if something goes wrong:

struct FileHandle: ~Copyable {
    let path: String
    
    consuming func close() throws {
        // Cleanup code that might fail
        if somethingBadHappened {
            throw CloseError.failed
        }
        discard self
    }
}

What makes this interesting is that Swift guarantees cleanup even when errors happen. It’s like having an invisible safety net under your juggling act. Here’s how it works in practice:

func processFile() throws {
    let file = FileHandle(path: "/important/data")
    
    do {
        try doSomethingRisky(with: file)
    } catch {
        // Even if we throw, Swift makes sure the file gets cleaned up
        throw error
    }
}

This is different from Rust’s approach. In Rust, cleanup is more rigid:

struct FileHandle {
    path: String,
}

impl Drop for FileHandle {
    fn drop(&mut self) {
        // Cleanup happens here - no throwing allowed!
    }
}

Rust’s philosophy is “cleanup must never fail” - it’s like having to guarantee you can always catch what you’re juggling. Swift gives you more flexibility but also more responsibility.

Safe Resource Transfers

Here’s a real-world pattern for safely transferring ownership when things might go wrong:

struct Connection: ~Copyable {
    let id: String
    
    consuming func transferTo(_ pool: inout ConnectionPool) throws {
        if pool.isFull {
            throw PoolError.full
            // Connection cleanup happens automatically here
        }
        pool.add(consuming: self)
    }
}

This is like trying to hand off a valuable item - if the recipient can’t take it (pool is full), you need to handle it safely yourself. Swift makes sure nothing gets lost in the process.

Async and Noncopyable: When Ownership Meets Concurrency

Here’s where things get really interesting. Handling noncopyable types in async code shows some fascinating patterns, especially when we need to manage resources across different tasks.

Let’s look at a common scenario - managing a database connection in async code:

struct AsyncConnection: ~Copyable {
    let id: String
    
    consuming func query(_ sql: String) async throws -> Results {
        // Run the query
        // Connection is consumed after the query
        return results
    }
}

But what happens when we want to reuse this connection for multiple queries? We need a different pattern:

actor ConnectionManager {
    private var connection: AsyncConnection
    
    func runQuery(_ sql: String) async throws -> Results {
        // The actor protects our connection from concurrent access
        return try await connection.query(sql)
    }
}

This is different from Rust’s approach with async:

struct AsyncConnection {
    id: String,
}

impl AsyncConnection {
    async fn query(&mut self, sql: &str) -> Results {
        // Connection remains valid after query
        // thanks to borrowing
    }
}

The key difference is that Rust’s borrow checker works seamlessly with async code, while Swift needs explicit actor isolation to handle concurrent access to noncopyable types.

Here’s a real-world example combining connection pools with async:

actor ConnectionPool {
    private var connections: [AsyncConnection] = []
    
    func checkout() async throws -> AsyncConnection {
        while connections.isEmpty {
            try await Task.sleep(nanoseconds: 1_000_000)  // Wait for a connection
        }
        return connections.take(at: 0)!
    }
    
    func return(_ conn: consuming AsyncConnection) {
        connections.append(consuming: conn)
    }
}

This pattern ensures that connections are safely managed even in concurrent code. The actor prevents any data races, while the noncopyable type ensures each connection is uniquely owned.

Under the Hood - How Swift’s ~Copyable Actually Works

When you mark a type as ~Copyable in Swift, several fascinating things happen behind the scenes. Let’s peek under the hood:

  1. Memory Management Changes
// Regular Swift struct
struct RegularStruct {
    let value: String
}
// Behind the scenes: Reference counting and copy-on-write

// Noncopyable struct
struct UniqueStruct: ~Copyable {
    let value: String
}
// Behind the scenes: Direct memory moves, no reference counting

Swift’s implementation uses a concept similar to Rust’s “move semantics” at the LLVM level. When a value is moved:

  • The memory location is transferred to the new owner.
  • The original memory location is marked as invalid.
  • No actual copying of data occurs.
  • The compiler maintains an ownership graph - it tracks where every noncopyable value is at all times. This is like having a master map showing where every unique item is currently located.

This is more complex than Rust’s approach, which handles most of these checks at compile time. But Swift’s approach gives us more flexibility in certain scenarios, especially when dealing with dynamic patterns in Swift’s more object-oriented world.

The Compiler’s Role

The Swift compiler performs several key tasks for noncopyable types:

  1. Ownership Tracking:
func example() {
    let ticket = SingleUseTicket(ticketID: "A123")
    processTicket(ticket) // Compiler tracks that 'ticket' is moved here
    // print(ticket) // Compiler prevents this - ownership already transferred
}
  1. Static Analysis: The compiler maintains an ownership graph, tracking:
  • Where values are created.
  • How they’re moved between variables.
  • When they’re consumed.
  • When they go out of scope.

Rust vs Swift: Implementation Differences

Let’s compare how both languages handle moves:

// Rust
struct Resource {
    data: String
}

fn main() {
    let x = Resource { data: String::from("hello") };
    let y = x; // Move occurs here
    // x.data // Error: use of moved value
}
// Swift
struct Resource: ~Copyable {
    let data: String
}

func main() {
    let x = Resource(data: "hello")
    let y = x // Move occurs here
    // x.data // Error: value consumed
}

The key difference is that Rust’s borrow checker is more sophisticated, handling:

  • Multiple references.
  • Lifetimes.
  • Complex ownership scenarios.

Swift’s implementation is currently simpler, focusing on:

  • Single ownership.
  • Move semantics.
  • Basic consumption rules.

Performance Implications

The ~Copyable attribute can lead to better performance because:

  1. No reference counting overhead.
  2. No copy-on-write machinery.
  3. Direct memory moves instead of allocations.

Wrap up

Swift 5.9’s introduction of noncopyable types marks a significant evolution in how we manage unique resources in Swift. Taking inspiration from Rust’s battle-tested ownership system while adding its own Swift-specific features, this addition provides developers with powerful tools to prevent resource duplication and ensure safer code.

Key takeaways:

  • Noncopyable types ensure unique ownership of resources.
  • Moving instead of copying prevents accidental resource duplication.
  • Three ways to share: consuming, borrowing, and inout.
  • Built-in safety for collections and error handling.
  • First-class support for async operations.

Whether you’re managing database connections, file handles, or any other unique resources, noncopyable types give you compiler-enforced guarantees that these resources will be handled correctly. The system is flexible enough to work with Swift’s existing features while being strict enough to prevent common programming errors.

This feature represents not just an addition to Swift’s type system, but a fundamental shift in how we can think about and manage resources in our applications. As developers begin to adopt these patterns, we’ll likely see more robust and safer applications emerge as a result.