axiom-ownership-conventions について
Swift の所有権とメモリアクセスに関する開発者向けリファレンスです。borrowing、consuming、InlineArray、Span、値ジェネリクスを扱い、性能重視のコードや非コピー型の制約を説明します。
主な機能
- 01borrowing と consuming の使い方
- 02~Copyable 型の制約
- 03InlineArray による固定サイズ格納
- 04Span による安全な連続メモリアクセス
- 05値ジェネリクスと型レベルのサイズ
活用シーン
- 01大きな値型のコピー削減
- 02非コピー型向け API の設計
- 03固定サイズデータのヒープ割り当て回避
- 04UnsafeBufferPointer を Span に置き換える
- 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 | 値が格納されます |
メソッドの 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: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 と危険なポインターの比較
| 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