在值型別篇中,我們將 Swift 的預設行為整理為「複製」:指派會複製,傳入函式也會複製。
這是上一篇文章 Swift 深入解析 #7 的延續。
但如果有些值不應該被複製呢?例如檔案控制代碼、互斥鎖、銀行轉帳權杖等「只有世上唯一一份才有意義」的資源。
本篇深入系列的主題是所有權(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
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——傳入函式的三種方式
要將不可複製值傳入函式時,會出現新的問題。既然不能複製,就必須決定要借出還是轉移。
參數修飾詞就是這項宣告。
**borrowing 是借用。**函式只讀取值,所有權仍留在呼叫端。
函式返回後,呼叫端仍可繼續使用該值。func checksum(of handle: borrowing FileHandle) -> Int適合這類唯讀操作。
**consuming 是移交。**所有權轉移給函式,呼叫端不能再使用該值。上面的 consuming func close() 正是這個意義。
呼叫 close 後再使用控制代碼會變成編譯錯誤;「再次使用已關閉控制代碼」這類錯誤從語法層面消失了。
它也能用來建模銀行轉帳權杖、一次性票券等「使用後就應該消失」的領域概念。
**inout 維持原本的方式,也就是借出後修改。**三者並列,就完整構成函式參數的所有權詞彙:只讀(borrowing)、取走(consuming)、修改(inout)。
這些修飾詞也能套用在 Copyable 型別上。此時它們不是語意要求,而是效能提示。
將預設慣例可能產生的 retain/release 或複製改以借用、移動取代,藉此降低 ARC 流量。使用 consume 運算子(let b = consume a)要求明確移動,也屬於同一類。
不過,這是必須先進行量測的微最佳化領域。關於分派便利性的警告在此同樣適用。
實務判斷——在哪裡使用、在哪裡不用
準確掌握這項功能目前的位置十分重要。
**適合的場景是資源的唯一擁有權。**例如檔案與通訊端控制代碼包裝器、鎖權杖、交易防護器、硬體存取權等。
事實上,這項功能的主要推動力之一就是 Embedded Swift。在堆積與 ARC 都負擔沉重的微控制器環境中,需要一種不使用類別也能安全管理資源的工具。
標準程式庫 Mutex 傳遞的值,以及 Span 等新型別,都屬於這個脈絡。
**一般 App 程式碼的預設仍是 Copyable struct。**以值為核心的原則沒有改變。
預先將 ~Copyable 套用到領域資料是過度設計。它與泛型生態系仍有摩擦(不可複製型別無法直接與假設 Copyable 的既有泛型和集合混用,語言仍在逐步放寬限制)。
目前應將它保留為專用工具,只用在能對「複製會是錯誤嗎?」回答「是」的型別上。
**以 Rust 的比較作結,**Rust 對所有值強制套用所有權規則,甚至要求生命週期標註,是「所有權預設」的語言。
Swift 維持複製預設的世界,只將需要的型別移入所有權世界,採取「選擇性所有權」。
這是 Progressive Disclosure 哲學的典型。不了解所有權的開發者,其程式碼完全不會出現這個概念;只有需要的人才會開啟下一階段。
總結
- 所有型別都隱式為 Copyable,而資源型別的未經允許複製,是重複釋放類錯誤的根源。
- ~Copyable(SE-0390)禁止複製,將指派改為移動,並讓使用已消費變數成為編譯錯誤。struct 也允許 deinit,因此能進行確定性清理。
- 參數所有權詞彙:borrowing(只讀、借用)、consuming(取走、移交)、inout(修改)。consuming 方法將「使用後消失」的語意刻進語法。
- 適用於資源的唯一擁有權(控制代碼、鎖、權杖、嵌入式環境);一般資料的預設仍是 Copyable struct。不同於 Rust 的全面所有權,Swift 採取選擇性所有權。
下一篇是深入系列,也是整個 Swift 系列的最後主題:揭開「Swift 5 中 App 容量突然變小」事件的始末——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 與不可複製型別規則

![[Swift 深入解析 #8] ~Copyable 與所有權、禁止複製的型別 封面圖](/assets/images/posts/d9a5f7c8-fa35-4590-aca1-b3e4e60f6a6b/swift-noncopyable-ownership-move.jpg)