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. 04UnsafeBufferPointer を Span に置き換える
  5. 05バイナリ解析とシリアライズの実装

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-ownership-conventions

インストール後、Claude Code で/axiom-ownership-conventionsを入力して読み込まれたか確認します。

詳細

borrowing & consuming — パラメーターの所有権

パフォーマンス最適化と非コピー可能な型のサポートのための、明示的な所有権修飾子です。

使用する場面

✅ 次の場合に使用します:

  • 大きな値型を読み取り専用で渡す場合(コピーを回避)
  • 非コピー可能な型(~Copyable)を扱う場合
  • ARC の retain/release 処理を減らす場合
  • ビルダーオブジェクトを消費するファクトリーメソッド
  • プロファイリングでコピーが確認された、パフォーマンスが重要なコード

❌ 次の場合は使用しないでください:

  • 単純な型(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:borrowing で十分な場面で 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 クロージャーパラメーターを使います
existential をサポートしない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

コメント