home / skills / charleswiltgen / axiom / axiom-ownership-conventions
/.claude-plugin/plugins/axiom/skills/axiom-ownership-conventions
This skill helps you optimize large value type performance by applying explicit borrowing and consuming ownership to reduce copies and ARC traffic.
npx playbooks add skill charleswiltgen/axiom --skill axiom-ownership-conventionsReview the files below or copy the command above to add this skill to your agents.
---
name: axiom-ownership-conventions
description: Use when optimizing large value type performance, working with noncopyable types, or reducing ARC traffic. Covers borrowing, consuming, inout modifiers, consume operator, ~Copyable types.
license: MIT
metadata:
version: "1.0.0"
---
# 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
| Modifier | Ownership | Copies | Use Case |
|----------|-----------|--------|----------|
| (default) | Compiler chooses | Implicit | Most cases |
| `borrowing` | Caller keeps | Explicit `copy` only | Read-only, large types |
| `consuming` | Caller transfers | None needed | Final use, factories |
| `inout` | Caller keeps, mutable | None | Modify in place |
## Default Behavior by Context
| Context | Default | Reason |
|---------|---------|--------|
| Function parameters | `borrowing` | Most params are read-only |
| Initializer parameters | `consuming` | Usually stored in properties |
| Property setters | `consuming` | Value is stored |
| Method `self` | `borrowing` | Methods read self |
## Patterns
### Pattern 1: Read-Only Large Struct
```swift
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
```swift
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:
```swift
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:
```swift
let data = loadLargeData()
process(consume data)
// data is now invalid — compiler prevents use
```
### Pattern 5: Noncopyable Type
For `~Copyable` types, ownership modifiers are **required**:
```swift
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
```swift
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
```swift
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
```swift
// ❌ 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
```swift
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
```swift
// ❌ 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
}
```
## 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
This skill documents clear ownership conventions for Swift-style parameter modifiers (borrowing, consuming, inout, consume operator) to optimize large value types, support noncopyable (~Copyable) types, and reduce ARC traffic. It bundles practical patterns, default behaviors, and a decision flow to help you choose when to borrow, consume, or let the compiler decide. Use it when performance or ownership safety matters in modern xOS codebases.
The guidance inspects call-site intent and type characteristics to recommend explicit ownership modifiers: borrowing for read-only access without implicit copies, consuming for transferring ownership (no copies), and inout for in-place mutation. It highlights when copy operations must be explicit and when ownership modifiers are required for ~Copyable types. It also points out ARC-reduction opportunities for reference types.
When should I prefer borrowing over consuming?
Prefer borrowing when you only need read-only access and want to avoid copies or ARC operations; use consuming when the callee must take ownership or the value is no longer needed by the caller.
Are these modifiers required for ~Copyable types?
Yes. For noncopyable (~Copyable) types, ownership modifiers are mandatory to express safe transfer or temporary access.
Will I lose ABI stability by adding ownership modifiers?
Modifiers can affect ABI; prefer them for internal performance-critical APIs and rely on profiling before changing widely used public interfaces.