askill
axiom-ownership-conventions

axiom-ownership-conventionsSafety --Repository

Use when optimizing large value type performance, working with noncopyable types, or reducing ARC traffic. Covers borrowing, consuming, inout modifiers, consume operator, ~Copyable types.

640 stars
12.8k downloads
Updated 3/16/2026

Package Files

Loading files...
SKILL.md

borrowing & consuming — Parameter Ownership

Explicit ownership modifiers for performance optimization and noncopyable type support.

When to Use

Use when:

  • Large value types being passed read-only (avoid copies)
  • Working with noncopyable types (~Copyable)
  • Reducing ARC retain/release traffic
  • Factory methods that consume builder objects
  • Performance-critical code where copies show in profiling

Don't use when:

  • Simple types (Int, Bool, small structs)
  • Compiler optimization is sufficient (most cases)
  • Readability matters more than micro-optimization
  • You're not certain about the performance impact

Quick Reference

ModifierOwnershipCopiesUse Case
(default)Compiler choosesImplicitMost cases
borrowingCaller keepsExplicit copy onlyRead-only, large types
consumingCaller transfersNone neededFinal use, factories
inoutCaller keeps, mutableNoneModify in place

Default Behavior by Context

ContextDefaultReason
Function parametersborrowingMost params are read-only
Initializer parametersconsumingUsually stored in properties
Property settersconsumingValue is stored
Method selfborrowingMethods read self

Patterns

Pattern 1: Read-Only Large Struct

struct LargeBuffer {
    var data: [UInt8]  // Could be megabytes
}

// ❌ Default may copy
func process(_ buffer: LargeBuffer) -> Int {
    buffer.data.count
}

// ✅ Explicit borrow — no copy
func process(_ buffer: borrowing LargeBuffer) -> Int {
    buffer.data.count
}

Pattern 2: Consuming Factory

struct Builder {
    var config: Configuration

    // Consumes self — builder invalid after call
    consuming func build() -> Product {
        Product(config: config)
    }
}

let builder = Builder(config: .default)
let product = builder.build()
// builder is now invalid — compiler error if used

Pattern 3: Explicit Copy in Borrowing

With borrowing, copies must be explicit:

func store(_ value: borrowing LargeValue) {
    // ❌ Error: Cannot implicitly copy borrowing parameter
    self.cached = value

    // ✅ Explicit copy
    self.cached = copy value
}

Pattern 4: Consume Operator

Transfer ownership explicitly:

let data = loadLargeData()
process(consume data)
// data is now invalid — compiler prevents use

Pattern 5: Noncopyable Type

For ~Copyable types, ownership modifiers are required:

struct FileHandle: ~Copyable {
    private let fd: Int32

    init(path: String) throws {
        fd = open(path, O_RDONLY)
        guard fd >= 0 else { throw POSIXError.errno }
    }

    borrowing func read(count: Int) -> Data {
        // Read without consuming handle
        var buffer = [UInt8](repeating: 0, count: count)
        _ = Darwin.read(fd, &buffer, count)
        return Data(buffer)
    }

    consuming func close() {
        Darwin.close(fd)
        // Handle consumed — can't use after close()
    }

    deinit {
        Darwin.close(fd)
    }
}

// Usage
let file = try FileHandle(path: "/tmp/data.txt")
let data = file.read(count: 1024)  // borrowing
file.close()  // consuming — file invalidated

Pattern 6: Reducing ARC Traffic

class ExpensiveObject { /* ... */ }

// ❌ Default: May retain/release
func inspect(_ obj: ExpensiveObject) -> String {
    obj.description
}

// ✅ Borrowing: No ARC traffic
func inspect(_ obj: borrowing ExpensiveObject) -> String {
    obj.description
}

Pattern 7: Consuming Method on Self

struct Transaction {
    var amount: Decimal
    var recipient: String

    // After commit, transaction is consumed
    consuming func commit() async throws {
        try await sendToServer(self)
        // self consumed — can't modify or reuse
    }
}

Common Mistakes

Mistake 1: Over-Optimizing Small Types

// ❌ Unnecessary — Int is trivially copyable
func add(_ a: borrowing Int, _ b: borrowing Int) -> Int {
    a + b
}

// ✅ Let compiler optimize
func add(_ a: Int, _ b: Int) -> Int {
    a + b
}

Mistake 2: Forgetting Explicit Copy

func cache(_ value: borrowing LargeValue) {
    // ❌ Compile error
    self.values.append(value)

    // ✅ Explicit copy required
    self.values.append(copy value)
}

Mistake 3: Consuming When Borrowing Suffices

// ❌ Consumes unnecessarily — caller loses access
func validate(_ data: consuming Data) -> Bool {
    data.count > 0
}

// ✅ Borrow for read-only
func validate(_ data: borrowing Data) -> Bool {
    data.count > 0
}

~Copyable Limitations

Know the constraints before adopting ~Copyable:

LimitationImpactWorkaround
Can't store in Array, Dictionary, SetCollections require CopyableUse Optional<T> wrapper or manage manually
Can't use with most generics<T> implicitly means <T: Copyable>Use <T: ~Copyable> (requires library support)
Protocol conformance restrictedMost protocols require CopyableUse ~Copyable protocol definitions
Can't capture in closures by defaultClosures copy captured valuesUse borrowing closure parameters
No existential supportany ~Copyable doesn't workUse generics instead

Common compiler errors when adopting ownership modifiers:

// Error: "Cannot implicitly copy a borrowing parameter"
// Fix: Add explicit `copy` or change to consuming
func store(_ v: borrowing LargeValue) {
    self.cached = copy v  // ✅ Explicit copy
}

// Error: "Noncopyable type cannot be used with generic"
// Fix: Constrain generic to ~Copyable
func use<T: ~Copyable>(_ value: borrowing T) { }  // ✅

// Error: "Cannot consume a borrowing parameter"
// Fix: Change to consuming if you need ownership transfer
func takeOwnership(_ v: consuming FileHandle) { }  // ✅

// Error: "Missing 'consuming' or 'borrowing' modifier"
// Fix: ~Copyable types require explicit ownership on all methods
struct Token: ~Copyable {
    borrowing func peek() -> String { ... }   // ✅ Explicit
    consuming func redeem() { ... }           // ✅ Explicit
}

When NOT to use ~Copyable:

  • If you need collection storage (arrays, dictionaries)
  • If you need to work with existing generic APIs
  • If the type needs broad protocol conformance
  • Prefer consuming func on regular types as a lighter alternative for "use once" semantics

Performance Considerations

When Ownership Modifiers Help

  • Large structs (arrays, dictionaries, custom value types)
  • High-frequency function calls in tight loops
  • Reference types where ARC traffic is measurable
  • Noncopyable types (required, not optional)

When to Skip

  • Default behavior is almost always optimal
  • Small value types (primitives, small structs)
  • Code where profiling shows no benefit
  • API stability concerns (modifiers affect ABI)

Decision Tree

Need explicit ownership?
├─ Working with ~Copyable type?
│  └─ Yes → Required (borrowing/consuming)
├─ Large value type passed frequently?
│  ├─ Read-only? → borrowing
│  └─ Final use? → consuming
├─ ARC traffic visible in profiler?
│  ├─ Read-only? → borrowing
│  └─ Transferring ownership? → consuming
└─ Otherwise → Let compiler choose

Resources

Swift Evolution: SE-0377

WWDC: 2024-10170

Skills: axiom-swift-performance, axiom-swift-concurrency

Install

Download ZIP
Requires askill CLI v1.0+

AI Quality Score

AI review pending.

Metadata

Licenseunknown
Version-
Updated3/16/2026
PublisherCharlesWiltgen

Tags

apici-cd