关于 axiom-ownership-conventions
面向 Swift 开发者的所有权与内存访问参考,介绍 borrowing、consuming、InlineArray、Span 和值泛型,适用于性能优化及不可复制类型场景。
主要功能
- 01borrowing 与 consuming 用法
- 02~Copyable 类型限制
- 03InlineArray 固定尺寸存储
- 04Span 安全连续内存访问
- 05值泛型与类型尺寸约束
适用场景
- 01减少大型值类型的复制
- 02为不可复制类型设计 API
- 03在固定尺寸数据中避免堆分配
- 04用 Span 替代不安全缓冲区指针
- 05实现二进制解析与序列化
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-ownership-conventions安装后在 Claude Code 中输入/axiom-ownership-conventions确认已加载。
详细介绍
borrowing & consuming — 参数所有权
用于性能优化和支持不可复制类型的显式所有权修饰符。
适用场景
✅ 适用:
- 以只读方式传递大型值类型(避免复制)
- 使用不可复制类型(
~Copyable) - 减少 ARC 保留/释放开销
- 消耗构建器对象的工厂方法
- 在性能关键代码中,分析结果显示存在复制
❌ 不适用:
- 简单类型(Int、Bool、小型结构体)
- 编译器优化已足够(大多数情况)
- 可读性比微优化更重要
- 不确定性能影响
快速参考
| 修饰符 | 所有权 | 复制 | 用例 |
|---|---|---|---|
| (默认) | 由编译器选择 | 隐式 | 大多数情况 |
borrowing | 调用方保留 | 仅显式 copy | 只读、大型类型 |
consuming | 调用方转移 | 无需复制 | 最后一次使用、工厂 |
inout | 调用方保留,可变 | 无 | 原地修改 |
不同上下文中的默认行为
| 上下文 | 默认值 | 原因 |
|---|---|---|
| 函数参数 | borrowing | 大多数参数都是只读的 |
| 初始化器参数 | consuming | 通常会存储到属性中 |
| 属性设置器 | consuming | 值会被存储 |
方法 self | borrowing | 方法会读取 self |
模式
模式 1:只读大型结构体
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
}
模式 2:消耗型工厂
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
模式 3:在 borrowing 中显式复制
使用 borrowing 时,复制必须显式指定:
func store(_ value: borrowing LargeValue) {
// ❌ Error: Cannot implicitly copy borrowing parameter
self.cached = value
// ✅ Explicit copy
self.cached = copy value
}
模式 4:Consume 运算符
显式转移所有权:
let data = loadLargeData()
process(consume data)
// data is now invalid — compiler prevents use
模式 5:不可复制类型
对于 ~Copyable 类型,所有权修饰符是必需的:
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
模式 6:减少 ARC 开销
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
}
模式 7:在 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
}
}
常见错误
错误 1:过度优化小型类型
// ❌ 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
}
错误 2:忘记显式复制
func cache(_ value: borrowing LargeValue) {
// ❌ Compile error
self.values.append(value)
// ✅ Explicit copy required
self.values.append(copy value)
}
错误 3:无需消耗却使用 consuming
// ❌ 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 的限制
采用 ~Copyable 前,请了解以下约束:
| 限制 | 影响 | 解决方法 |
|---|---|---|
无法存储在 Array、Dictionary、Set 中 | 集合要求 Copyable | 使用 Optional<T> 包装器或手动管理 |
| 无法与大多数泛型搭配使用 | <T> 默认表示 <T: Copyable> | 使用 <T: ~Copyable>(需要库支持) |
| 协议遵循受限 | 大多数协议要求 Copyable | 使用 ~Copyable 协议定义 |
| 默认无法在闭包中捕获 | 闭包会复制捕获的值 | 使用 borrowing 闭包参数 |
| 不支持存在类型 | any ~Copyable 不可用 | 改用泛型 |
采用所有权修饰符时常见的编译器错误:
// 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
}
不应使用 ~Copyable 的情况:
- 需要存储在集合中(数组、字典)
- 需要使用现有的泛型 API
- 类型需要广泛遵循协议
- 对于“一次性使用”语义,优先考虑在常规类型上使用
consuming func,作为更轻量的替代方案
性能注意事项
所有权修饰符何时有帮助
- 大型结构体(数组、字典、自定义值类型)
- 紧密循环中的高频函数调用
- ARC 开销可测量的引用类型
- 不可复制类型(必需,而非可选)
何时跳过
- 默认行为几乎总是最优的
- 小型值类型(基本类型、小型结构体)
- 分析结果显示没有收益的代码
- API 稳定性方面的顾虑(修饰符会影响 ABI)
InlineArray
使用值泛型的固定大小栈分配数组。无堆分配、无引用计数、无写时复制。
声明
@frozen struct InlineArray<let count: Int, Element> where Element: ~Copyable
let count: Int 是一个值泛型——大小是类型的一部分,并在编译时检查。InlineArray<3, Int> 和 InlineArray<4, Int> 是不同的类型。
何时使用 InlineArray
| 使用 InlineArray | 使用 Array |
|---|---|
| 大小在编译时已知 | 大小会在运行时改变 |
| 热路径需要零堆分配 | 受益于写时复制共享 |
| 嵌入其他值类型中 | 经常在变量之间复制 |
| 对性能至关重要的内层循环 | 通用集合需求 |
典型示例
// Fixed-size, inline storage — no heap allocation
var matrix: InlineArray<9, Float> = [1, 0, 0, 0, 1, 0, 0, 0, 1]
matrix[4] = 2.0
// Type inference works for count, element, or both
let rgb: InlineArray = [0.2, 0.4, 0.8] // InlineArray<3, Double>
// Eager copy on assignment (no COW)
var copy = matrix
copy[0] = 99 // matrix[0] still 1
内存布局
元素连续存储,没有额外开销:
MemoryLayout<InlineArray<3, UInt16>>.size // 6 (2 bytes × 3)
MemoryLayout<InlineArray<3, UInt16>>.alignment // 2 (same as UInt16)
与 ~Copyable 集成
InlineArray 支持不可复制的元素,可用于创建唯一资源的定长集合:
struct Sensor: ~Copyable { var id: Int }
var sensors: InlineArray<4, Sensor> = ... // Valid: ~Copyable elements allowed
Span — 安全访问连续内存
Span 用编译时强制保证安全的内存视图取代不安全指针。运行时开销为零。
Span 系列
| 类型 | 访问方式 | 使用场景 |
|---|---|---|
Span<Element> | 只读元素 | 安全迭代、传递给算法 |
MutableSpan<Element> | 读写元素 | 原地修改,无需复制 |
RawSpan | 只读字节 | 二进制解析、协议解码 |
MutableRawSpan | 读写字节 | 二进制序列化 |
OutputSpan | 只写 | 初始化新的集合存储 |
UTF8Span | 只读 UTF-8 | 安全处理 Unicode |
访问 Span
具有连续存储的容器会提供 .span 和 .mutableSpan:
let array = [1, 2, 3, 4]
let span = array.span // Span<Int>
var mutable = [10, 20, 30]
var ms = mutable.mutableSpan // MutableSpan<Int>
ms[0] = 99
生命周期安全 — 编译时强制保证
Span 不可逃逸——编译器保证其生命周期不会超出所借用的容器:
// ❌ Cannot return span that depends on local variable
func getSpan() -> Span<UInt8> {
let array: [UInt8] = Array(repeating: 0, count: 128)
return array.span // Compile error
}
// ❌ Cannot capture span in closure
let span = array.span
let closure = { span.count } // Compile error
// ❌ Cannot access span after mutating original
var array = [1, 2, 3]
let span = array.span
array.append(4)
// span[0] // Compile error: container was modified
这些约束能在编译时防止释放后使用、悬空指针和重叠修改,且运行时开销为零。
Span 与不安全指针的对比
| Span | UnsafeBufferPointer | |
|---|---|---|
| 内存安全 | 编译时强制保证 | 手动管理,容易出错 |
| 生命周期跟踪 | 自动,不可逃逸 | 无——可能出现悬空指针 |
| 运行时开销 | 零 | 零 |
| 释放后使用 | 不可能 | 崩溃的常见原因 |
典型示例 — 二进制解析
func parseHeader(_ data: borrowing [UInt8]) -> Header {
var raw = data.span.rawSpan // RawSpan over the array's bytes
let magic = raw.unsafeLoadUnaligned(as: UInt32.self)
raw = raw.extracting(droppingFirst: 4)
let version = raw.unsafeLoadUnaligned(as: UInt16.self)
return Header(magic: magic, version: version)
}
何时使用 Span
- 取代
UnsafeBufferPointer— 性能相同,并提供编译时安全保证 - 对性能至关重要的算法 — 直接访问内存,无需复制
- 二进制解析/序列化 — 使用
RawSpan按字节访问 - 在函数之间传递数据 — 借用容器并传递 Span
- 处理 UTF-8 — 使用
UTF8Span安全访问字符串字节
值泛型
值泛型允许将整数值用作泛型参数,使大小成为类型系统的一部分:
// `let count: Int` is a value generic parameter
struct InlineArray<let count: Int, Element> { ... }
// Different counts = different types
let a: InlineArray<3, Int> = [1, 2, 3]
let b: InlineArray<4, Int> = [1, 2, 3, 4]
// a = b // Compile error: different types
目前仅支持 Int 参数。可用于创建栈分配的定长抽象,编译器会在编译时验证大小兼容性。
决策树
Need explicit ownership?
├─ Working with ~Copyable type?
│ └─ Yes → Required (borrowing/consuming)
├─ Fixed-size collection, no heap allocation?
│ └─ Yes → InlineArray<let count, Element>
├─ Need safe pointer-like access to contiguous memory?
│ ├─ Read-only? → Span<Element>
│ ├─ Mutable? → MutableSpan<Element>
│ └─ Raw bytes? → RawSpan / MutableRawSpan
├─ 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
资源
Swift Evolution:SE-0377、SE-0453(Span)、SE-0451(InlineArray)、SE-0452(值泛型)
WWDC:2024-10170、2025-245、2025-312
文档:/swift/inlinearray、/swift/span
技能:axiom-swift-performance、axiom-swift-concurrency