axiom-ownership-conventions by charleswiltgen/axiom
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-ownership-conventions用于性能优化和非可复制类型支持的显式所有权修饰符。
✅ 在以下情况使用:
~Copyable)❌ 不要在以下情况使用:
| 修饰符 | 所有权 | 复制行为 | 使用场景 |
|---|---|---|---|
| (默认) | 编译器选择 | 隐式复制 | 大多数情况 |
borrowing | 调用者保留 | 仅显式 |
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
copy| 只读、大型类型 |
consuming | 调用者转移 | 无需复制 | 最终使用、工厂方法 |
inout | 调用者保留,可变 | 无 | 原地修改 |
| 上下文 | 默认行为 | 原因 |
|---|---|---|
| 函数参数 | borrowing | 大多数参数是只读的 |
| 初始化器参数 | consuming | 通常存储在属性中 |
| 属性设置器 | consuming | 值被存储 |
方法中的 self | borrowing | 方法读取 self |
struct LargeBuffer {
var data: [UInt8] // 可能达到兆字节级别
}
// ❌ 默认可能复制
func process(_ buffer: LargeBuffer) -> Int {
buffer.data.count
}
// ✅ 显式借用 —— 无复制
func process(_ buffer: borrowing LargeBuffer) -> Int {
buffer.data.count
}
struct Builder {
var config: Configuration
// 消耗 self —— 调用后构建器无效
consuming func build() -> Product {
Product(config: config)
}
}
let builder = Builder(config: .default)
let product = builder.build()
// builder 现在无效 —— 如果使用会导致编译错误
使用 borrowing 时,复制必须是显式的:
func store(_ value: borrowing LargeValue) {
// ❌ 错误:无法隐式复制借用参数
self.cached = value
// ✅ 显式复制
self.cached = copy value
}
显式转移所有权:
let data = loadLargeData()
process(consume data)
// data 现在无效 —— 编译器阻止使用
对于 ~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 {
// 读取而不消耗句柄
var buffer = [UInt8](repeating: 0, count: count)
_ = Darwin.read(fd, &buffer, count)
return Data(buffer)
}
consuming func close() {
Darwin.close(fd)
// 句柄被消耗 —— 关闭后无法使用
}
deinit {
Darwin.close(fd)
}
}
// 使用示例
let file = try FileHandle(path: "/tmp/data.txt")
let data = file.read(count: 1024) // borrowing
file.close() // consuming —— file 被置为无效
class ExpensiveObject { /* ... */ }
// ❌ 默认:可能保留/释放
func inspect(_ obj: ExpensiveObject) -> String {
obj.description
}
// ✅ 借用:无 ARC 开销
func inspect(_ obj: borrowing ExpensiveObject) -> String {
obj.description
}
struct Transaction {
var amount: Decimal
var recipient: String
// 提交后,事务被消耗
consuming func commit() async throws {
try await sendToServer(self)
// self 被消耗 —— 无法修改或重用
}
}
// ❌ 不必要 —— Int 是轻量级可复制的
func add(_ a: borrowing Int, _ b: borrowing Int) -> Int {
a + b
}
// ✅ 让编译器优化
func add(_ a: Int, _ b: Int) -> Int {
a + b
}
func cache(_ value: borrowing LargeValue) {
// ❌ 编译错误
self.values.append(value)
// ✅ 需要显式复制
self.values.append(copy value)
}
// ❌ 不必要地消耗 —— 调用者失去访问权限
func validate(_ data: consuming Data) -> Bool {
data.count > 0
}
// ✅ 对于只读操作使用借用
func validate(_ data: borrowing Data) -> Bool {
data.count > 0
}
在采用 ~Copyable 前了解其约束:
| 限制 | 影响 | 解决方法 |
|---|---|---|
无法存储在 Array、Dictionary、Set 中 | 集合需要 Copyable | 使用 Optional<T> 包装器或手动管理 |
| 无法与大多数泛型一起使用 | <T> 隐式意味着 <T: Copyable> | 使用 <T: ~Copyable>(需要库支持) |
| 协议一致性受限 | 大多数协议需要 Copyable | 使用 ~Copyable 协议定义 |
| 默认无法在闭包中捕获 | 闭包复制捕获的值 | 使用 borrowing 闭包参数 |
| 无存在类型支持 | any ~Copyable 无效 | 改用泛型 |
采用所有权修饰符时的常见编译器错误:
// 错误:"无法隐式复制借用参数"
// 修复:添加显式 `copy` 或改为 consuming
func store(_ v: borrowing LargeValue) {
self.cached = copy v // ✅ 显式复制
}
// 错误:"非可复制类型不能与泛型一起使用"
// 修复:将泛型约束为 ~Copyable
func use<T: ~Copyable>(_ value: borrowing T) { } // ✅
// 错误:"无法消耗借用参数"
// 修复:如果需要所有权转移,改为 consuming
func takeOwnership(_ v: consuming FileHandle) { } // ✅
// 错误:"缺少 'consuming' 或 'borrowing' 修饰符"
// 修复:~Copyable 类型的所有方法都需要显式所有权
struct Token: ~Copyable {
borrowing func peek() -> String { ... } // ✅ 显式
consuming func redeem() { ... } // ✅ 显式
}
何时不使用 ~Copyable:
consuming func 作为更轻量的替代方案使用值泛型的固定大小、栈分配数组。无堆分配,无引用计数,无写时复制。
@frozen struct InlineArray<let count: Int, Element> where Element: ~Copyable
let count: Int 是一个值泛型 —— 大小是类型的一部分,在编译时检查。InlineArray<3, Int> 和 InlineArray<4, Int> 是不同的类型。
| 使用 InlineArray | 使用 Array |
|---|---|
| 大小在编译时已知 | 大小在运行时变化 |
| 需要零堆分配的热路径 | 写时复制共享有益处 |
| 嵌入其他值类型中 | 在变量间频繁复制 |
| 性能关键的内循环 | 通用集合需求 |
// 固定大小,内联存储 —— 无堆分配
var matrix: InlineArray<9, Float> = [1, 0, 0, 0, 1, 0, 0, 0, 1]
matrix[4] = 2.0
// 类型推断适用于数量、元素或两者
let rgb: InlineArray = [0.2, 0.4, 0.8] // InlineArray<3, Double>
// 赋值时立即复制(无 COW)
var copy = matrix
copy[0] = 99 // matrix[0] 仍为 1
元素连续存储,无开销:
MemoryLayout<InlineArray<3, UInt16>>.size // 6 (2 字节 × 3)
MemoryLayout<InlineArray<3, UInt16>>.alignment // 2 (与 UInt16 相同)
InlineArray 支持非可复制元素 —— 支持固定大小的唯一资源集合:
struct Sensor: ~Copyable { var id: Int }
var sensors: InlineArray<4, Sensor> = ... // 有效:允许 ~Copyable 元素
Span 用编译时强制安全的内存视图替换不安全的指针。零运行时开销。
| 类型 | 访问权限 | 使用场景 |
|---|---|---|
Span<Element> | 只读元素 | 安全迭代,传递给算法 |
MutableSpan<Element> | 读写元素 | 原地修改,无需复制 |
RawSpan | 只读字节 | 二进制解析,协议解码 |
MutableRawSpan | 读写字节 | 二进制序列化 |
OutputSpan | 只写 | 初始化新集合存储 |
UTF8Span | 只读 UTF-8 | 安全的 Unicode 处理 |
具有连续存储的容器暴露 .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 是不可逃逸的 —— 编译器保证它们不能比它们借用的容器存活更久:
// ❌ 无法返回依赖于局部变量的 span
func getSpan() -> Span<UInt8> {
let array: [UInt8] = Array(repeating: 0, count: 128)
return array.span // 编译错误
}
// ❌ 无法在闭包中捕获 span
let span = array.span
let closure = { span.count } // 编译错误
// ❌ 修改原始容器后无法访问 span
var array = [1, 2, 3]
let span = array.span
array.append(4)
// span[0] // 编译错误:容器已被修改
这些约束在编译时防止了释放后使用、悬垂指针和重叠修改,且零运行时成本。
| Span | UnsafeBufferPointer
---|---|---
内存安全 | 编译时强制 | 手动,易出错
生命周期跟踪 | 自动,不可逃逸 | 无 —— 可能出现悬垂指针
运行时开销 | 零 | 零
释放后使用 | 不可能 | 常见的崩溃来源
func parseHeader(_ data: borrowing [UInt8]) -> Header {
var raw = data.span.rawSpan // 数组字节上的 RawSpan
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)
}
UnsafeBufferPointer —— 相同性能,编译时安全RawSpanUTF8Span值泛型允许将整数值作为泛型参数,使大小成为类型系统的一部分:
// `let count: Int` 是一个值泛型参数
struct InlineArray<let count: Int, Element> { ... }
// 不同数量 = 不同类型
let a: InlineArray<3, Int> = [1, 2, 3]
let b: InlineArray<4, Int> = [1, 2, 3, 4]
// a = b // 编译错误:不同类型
目前仅限于 Int 参数。支持栈分配的固定大小抽象,编译器在编译时验证大小兼容性。
需要显式所有权吗?
├─ 处理 ~Copyable 类型?
│ └─ 是 → 必需 (borrowing/consuming)
├─ 固定大小集合,无堆分配?
│ └─ 是 → InlineArray<let count, Element>
├─ 需要安全的类似指针的连续内存访问?
│ ├─ 只读? → Span<Element>
│ ├─ 可变? → MutableSpan<Element>
│ └─ 原始字节? → RawSpan / MutableRawSpan
├─ 大型值类型频繁传递?
│ ├─ 只读? → borrowing
│ └─ 最终使用? → consuming
├─ 分析器中可见 ARC 开销?
│ ├─ 只读? → borrowing
│ └─ 转移所有权? → consuming
└─ 否则 → 让编译器选择
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
每周安装数
89
仓库
GitHub 星标数
606
首次出现
2026年1月21日
安全审计
安装于
opencode73
claude-code69
codex68
gemini-cli66
cursor66
github-copilot63
Explicit ownership modifiers for performance optimization and noncopyable type support.
✅ Use when:
~Copyable)❌ Don't use when:
| 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 |
| 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 |
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
}
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
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
}
Transfer ownership explicitly:
let data = loadLargeData()
process(consume data)
// data is now invalid — compiler prevents use
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
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
}
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
}
}
// ❌ 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
}
func cache(_ value: borrowing LargeValue) {
// ❌ Compile error
self.values.append(value)
// ✅ Explicit copy required
self.values.append(copy value)
}
// ❌ 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
}
Know the constraints before adopting ~Copyable:
| Limitation | Impact | Workaround |
|---|---|---|
Can't store in Array, Dictionary, Set | Collections require Copyable | Use Optional<T> wrapper or manage manually |
| Can't use with most generics | <T> implicitly means <T: Copyable> | Use <T: ~Copyable> (requires library support) |
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:
consuming func on regular types as a lighter alternative for "use once" semanticsFixed-size, stack-allocated array using value generics. No heap allocation, no reference counting, no copy-on-write.
@frozen struct InlineArray<let count: Int, Element> where Element: ~Copyable
The let count: Int is a value generic — the size is part of the type, checked at compile time. InlineArray<3, Int> and InlineArray<4, Int> are different types.
| Use InlineArray | Use Array |
|---|---|
| Size known at compile time | Size changes at runtime |
| Hot path needing zero heap allocation | Copy-on-write sharing is beneficial |
| Embedded in other value types | Frequently copied between variables |
| Performance-critical inner loops | General-purpose collection needs |
// 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
Elements are stored contiguously with no overhead:
MemoryLayout<InlineArray<3, UInt16>>.size // 6 (2 bytes × 3)
MemoryLayout<InlineArray<3, UInt16>>.alignment // 2 (same as UInt16)
InlineArray supports noncopyable elements — enables fixed-size collections of unique resources:
struct Sensor: ~Copyable { var id: Int }
var sensors: InlineArray<4, Sensor> = ... // Valid: ~Copyable elements allowed
Span replaces unsafe pointers with compile-time-enforced safe memory views. Zero runtime overhead.
| Type | Access | Use Case |
|---|---|---|
Span<Element> | Read-only elements | Safe iteration, passing to algorithms |
MutableSpan<Element> | Read-write elements | In-place mutation without copies |
RawSpan | Read-only bytes | Binary parsing, protocol decoding |
MutableRawSpan | Read-write bytes | Binary serialization |
OutputSpan | Write-only |
Containers with contiguous storage expose .span and .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
Spans are non-escapable — the compiler guarantees they cannot outlive the container they borrow from:
// ❌ 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
These constraints prevent use-after-free, dangling pointers, and overlapping mutation at compile time with zero runtime cost.
| Span | UnsafeBufferPointer
---|---|---
Memory safety | Compile-time enforced | Manual, error-prone
Lifetime tracking | Automatic, non-escapable | None — dangling pointers possible
Runtime overhead | Zero | Zero
Use-after-free | Impossible | Common source of crashes
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)
}
UnsafeBufferPointer — same performance, compile-time safetyRawSpan for byte-level accessUTF8Span for safe string byte accessValue generics allow integer values as generic parameters, making sizes part of the type system:
// `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
Currently limited to Int parameters. Enables stack-allocated, fixed-size abstractions where the compiler verifies size compatibility at compile time.
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 (value generics)
WWDC : 2024-10170, 2025-245, 2025-312
Docs : /swift/inlinearray, /swift/span
Skills : axiom-swift-performance, axiom-swift-concurrency
Weekly Installs
89
Repository
GitHub Stars
606
First Seen
Jan 21, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode73
claude-code69
codex68
gemini-cli66
cursor66
github-copilot63
TanStack Query v5 完全指南:React 数据管理、乐观更新、离线支持
2,500 周安装
| Protocol conformance restricted | Most protocols require Copyable | Use ~Copyable protocol definitions |
| Can't capture in closures by default | Closures copy captured values | Use borrowing closure parameters |
| No existential support | any ~Copyable doesn't work | Use generics instead |
| Initializing new collection storage |
UTF8Span | Read-only UTF-8 | Safe Unicode processing |