Swift & Objective-C

[Swift深掘り #8] ~Copyableと所有権、コピー禁止の型

ファイルハンドルやロックのように一つでこそ意味を持つリソースは、コピーすべきではありません。Swift 5.9の~Copyableでコピー禁止を型に刻む方法と、borrowing・consuming・inoutの3つの受け渡し方、使いどころを整理します。

読了 8 分
[Swift深掘り #8] ~Copyableと所有権、コピー禁止の型のカバー画像

値型の回で、Swiftの基本動作を「コピー」として整理しました。代入するとコピーされ、関数に渡してもコピーされます。

前回の記事Swift深掘り #7の続きです。

では、コピーしてはいけない値があるとしたらどうでしょう。ファイルハンドル、ミューテックスロック、銀行振込トークンのように、「世の中に一つだけでこそ意味を持つ」リソースです。

深掘りシリーズ第8回のテーマは所有権(ownership)です。Swift 5.9の~Copyable(非コピー型)とborrowing・consumingが、その扉を開きます。SE-0390: Noncopyable Structs and Enums

Rustを知っている方なら、聞き覚えのある考え方でしょう。そうです。SwiftがRustの核心的なアイデアを独自の形で取り入れた領域です。

ただし方向性は異なります。Rustは所有権が基本で例外がありませんが、Swiftはコピーが基本で、所有権は選択できる道具です。

コピー可能が基本の世界にある穴

Swiftのすべての型は、デフォルトでCopyableです。明示しなくても、コンパイラが暗黙に採用させるプロトコルです。

値型の回で見た、代入や受け渡しにおけるコピーの意味論はここから来ています。ほとんどの値(数値、文字列、座標)には、完璧なデフォルトです。

問題は、リソースを表す型です。ファイルディスクリプタを包むstructを考えてみましょう。

struct FileHandle {
    let fd: Int32
    func close() { /* fd 閉じる */ }
}

let a = FileHandle(fd: open("data.txt"))
let b = a          // コピー — 同じ fdを2つが保持
a.close()
b.close()          // すでに閉じた fdを再び閉じる — 未定義動作

ハンドルがコピーされた瞬間、「誰が閉じる責任を持つのか」が曖昧になります。二重解放や閉じたハンドルの使用といったリソースバグの根は、すべてこの無断コピーです。

これまでの慣用的な対処は、クラスにしてdeinitで閉じることでした。参照を一つにして管理主体を統一する方法です。

動作はします。ただし、ARC(Automatic Reference Counting、自動参照カウント)の回で見たコスト、つまりヒープと参照カウントの負担が発生します。

しかも「コピーしてはいけない」というルール自体を、依然としてコンパイラは知りません。

~Copyable — コピー禁止を型に刻む

Swift 5.9の答えが非コピー型です(Swift Evolution提案SE-0390)。チルダ付きの~Copyableは「Copyableを採用しない」という宣言です。SE-0390: Noncopyable Structs and Enums

struct FileHandle: ~Copyable {
    let fd: Int32

    consuming func close() { /* fd 閉じる */ }
    deinit { /* 閉じていなければここで閉じる */ }
}

let a = FileHandle(fd: open("data.txt"))
let b = a          // コピーではなく移動 — 所有権が bへ移る
// print(a.fd)     // コンパイルエラー: aはすでに消費済み

動作が根本から変わります。代入はコピーではなく移動(move)になり、所有権を渡した変数はその時点から使用禁止です。

違反はランタイムクラッシュではなくコンパイルエラーです。「この値の所有者は常に正確に一つ」というルールを、コンパイラが台帳のように管理します。

Optionalがnilを、Sendableがレースを型の問題にしたように、リソースの唯一性も型の問題になったのです。

さらに、structなのにdeinitを宣言できるため、所有者がスコープを抜けると確実に後片付けコードが実行されます。

クラスなしで、RAII(Resource Acquisition Is Initialization — リソース確保を初期化に結び付ける慣用句)形式のリソース管理が可能になります。

コピーによる二重解放バグと、移動によって防ぐ構造を対比した図
二重解放というバグクラスがコンパイルエラーに変わります

borrowingとconsuming — 関数に渡す3つの方法

非コピー値を関数に渡すと、新たな疑問が生じます。コピーできないなら、借りるのか、渡すのかを決めなければなりません。

それを宣言するのが、パラメータ修飾子です。

**borrowingは借用です。**関数は値を読むだけで、所有権は呼び出し側に残ります。

関数から戻った後も、呼び出し側は値を使い続けられます。func checksum(of handle: borrowing FileHandle) -> Int読み取り専用の処理に適しています。

**consumingは譲渡です。**所有権が関数へ移り、呼び出し側はその値を再利用できません。上のconsuming func close()がまさにこの意味です。

closeを呼んだ後のハンドル使用がコンパイルエラーになる。つまり「閉じたハンドルを再利用する」バグクラスが、構文によって消える瞬間です。

銀行振込トークンやワンタイムチケットのように、「使ったら消費されるべき」ドメイン概念をモデル化する道具でもあります。

**inoutは従来どおり、借りて変更を反映する方法です。**3つを並べると、関数パラメータの所有権語彙が完成します。読むだけ(borrowing)、受け取る(consuming)、変更する(inout)。

これらの修飾子はCopyable型にも付けられます。その場合は意味ではなく、性能のヒントになります。

通常の規約で発生しうるretain/releaseやコピーを借用・移動に置き換え、ARCのトラフィックを減らします。consume演算子(let b = consume a)で明示的な移動を要求するのも同じ系統です。

ただし、これは計測が先に必要なマイクロ最適化の領域です。ディスパッチの利便性に関する注意点も、ここにそのまま当てはまります。

実務感覚 — 使う場所、使わない場所

この機能の現在の位置付けを正確に押さえることが重要です。

**適しているのは、リソースを一意に所有する場面です。**ファイル・ソケットハンドルのラッパー、ロックトークン、トランザクションガード、ハードウェアへのアクセス権などです。

実際、この機能を強く後押ししたものの一つが組み込みSwiftでした。ヒープもARCも負担になるマイクロコントローラ環境では、クラスなしでリソースを安全に管理する道具が必要だったのです。

標準ライブラリのMutexが渡す値や、Spanのような新しい型は、この系譜にあります。

**一般的なアプリコードでは、デフォルトは依然としてCopyable structです。**値型を使いやすくする原則は変わりません。

ドメインデータに~Copyableを先回りして適用するのは過剰設計です。ジェネリックエコシステムとの摩擦もまだあります(非コピー型はCopyableを前提とする既存のジェネリックやコレクションとすぐには混在できず、言語は段階的に対応を進めています)。

「コピーされたらバグか」という問いに「そうだ」と答えられる型だけに使う、専門的な道具として扱うのが現時点のバランスです。

**Rustとの比較で締めると、**Rustはすべての値に所有権規則を強制し、ライフタイム注釈まで求める「所有権が基本」の言語です。

Swiftはコピーが基本の世界を維持しつつ、必要な型だけを所有権の世界へ移す「選択的所有権」を採用しました。

Progressive Disclosure哲学の典型です。所有権を知らない開発者のコードにはこの概念が一切現れず、必要な人にだけ段階的に道が開かれます。

borrowing・consuming・inoutの3つの受け渡し方を窓口にたとえた図
読むだけ(borrowing)、受け取る(consuming)、変更する(inout)

まとめ

  • すべての型は暗黙にCopyableであり、リソース型への無断コピーが二重解放などのバグの根です。
  • ~Copyable(SE-0390)はコピーを禁止し、代入を移動に変え、消費済み変数の使用をコンパイルエラーにします。structではdeinitも許可され、確実な後片付けが可能です。
  • パラメータ所有権の語彙は、borrowing(読むだけ、借用)、consuming(受け取る、譲渡)、inout(変更する)です。consumingメソッドは「使うと消える」意味を構文に刻みます。
  • 適用先はリソースの一意所有(ハンドル・ロック・トークン・組み込み)で、一般データのデフォルトは依然としてCopyable structです。Rustの全面的な所有権とは異なり、Swiftは選択的所有権です。

次回は深掘りシリーズ、そしてSwiftシリーズ全体の最後のテーマです。「Swift 5でアプリ容量が突然小さくなった」出来事の全貌、ABI(Application Binary Interface)安定性を扱います。SE-0390: Noncopyable Structs and Enums

出典と確認基準

  • SE-0390: Noncopyable Structs and Enums — Swift Evolution・標準・仕様原文・確認日 2026-08-17・根拠:Swift 5.9の~Copyable、consuming・borrowing、非コピー型の規則

あわせて読みたい