mcpField
A
axiom-ownership-conventions

axiom-ownership-conventions

github.com/charleswiltgen开发1.2k158 次安装今天更新
代码仓库

关于 axiom-ownership-conventions

面向 Swift 开发者的所有权与内存访问参考,介绍 borrowing、consuming、InlineArray、Span 和值泛型,适用于性能优化及不可复制类型场景。

主要功能

  1. 01borrowing 与 consuming 用法
  2. 02~Copyable 类型限制
  3. 03InlineArray 固定尺寸存储
  4. 04Span 安全连续内存访问
  5. 05值泛型与类型尺寸约束

适用场景

  1. 01减少大型值类型的复制
  2. 02为不可复制类型设计 API
  3. 03在固定尺寸数据中避免堆分配
  4. 04用 Span 替代不安全缓冲区指针
  5. 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值会被存储
方法 selfborrowing方法会读取 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 与不安全指针的对比
SpanUnsafeBufferPointer
内存安全编译时强制保证手动管理,容易出错
生命周期跟踪自动,不可逃逸无——可能出现悬空指针
运行时开销零零
释放后使用不可能崩溃的常见原因
典型示例 — 二进制解析
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

评论

支持 Markdown