原文連結:swift.gg/2018/08/30/…
作者:Matt
譯者:雨謹
校對:numbbbbb,wongzigii,Firecrest
定稿:CMB
Never
是一個約定,表示一件事在過去或未來的任何時段都不會發生。它是時間軸上的一種邏輯上的不可能,在任何方向延展開去都沒有可能。這就是為什麼在程式碼中看到 這樣的註釋 會特別讓人不安。
// 這個不會發生
複製程式碼
所有編譯器的教科書都會告訴你,這樣一句註釋不能也不會對編譯出的程式碼產生任何影響。墨菲定理 告訴你並非如此,註釋以下的程式碼一定會被觸發。
那 Swift 是如何在這種無法預測的、混亂的開發過程中保證安全呢?答案難以置信:“什麼都不做”,以及“崩潰”。
使用 Never
替換 @noreturn
修飾符,是由 Joe Groff 在 SE-0102: “Remove @noreturn attribute and introduce an empty Never type” 中提出的。
在 Swift 3 之前,那些要中斷執行的函式,比如 fatalError(_:file:line:)
,abort()
和 exit(_:)
,需要使用 @noreturn
修飾符來宣告,這會告訴編譯器,執行完成後不用返回撥用這個函式的位置。
// Swift < 3.0
@noreturn func fatalError(_ message: () -> String = String(),
file: StaticString = #file,
line: UInt = #line)
複製程式碼
從 Swift 3 開始,fatalError
和它的相關函式都被宣告為返回 Never
型別。
// Swift >= 3.0
func fatalError(_ message: @autoclosure () -> String = String(),
file: StaticString = #file,
line: UInt = #line) -> Never
複製程式碼
作為一個註釋的替代品,它肯定是很複雜的,對嗎?NO!事實上,恰恰相反,Never
可以說是整個 Swift 標準庫中最簡單的一個型別:
enum Never {}
複製程式碼
無例項型別(Uninhabited Types
)
Never
是一個_無例項_(Uninhabited)型別,也就是說它沒有任何值。或者換句話說,無例項型別是無法被構建的。
在 Swift 中,沒有定義任何 case
的列舉是最常見的一種無例項型別。跟結構體和類不同,列舉沒有初始化方法。跟協議也不同,列舉是一個具體的型別,可以包含屬性、方法、泛型約束和巢狀型別。正因如此,Swift 標準庫廣泛使用無例項的列舉型別來做諸如 定義名稱空間 以及 標識型別的含義 之類的事情。
但 Never
並不這樣。它沒有什麼花哨的東西,它的特別之處就在於,它就是它自己(或者說,它什麼都不是)。
試想一個返回值為無例項型別的函式:因為無例項型別沒有任何值,所以這個函式無法正常的返回。(它要如何生成這個返回值呢?)所以,這個函式要麼停止執行,要麼無休止的一直執行下去。
消除泛型中的不可能狀態
從理論角度上說,Never
確實很有意思,但它在實際應用中又能幫我們做什麼呢?
做不了什麼,或者說在 SE-0215: Conform Never to Equatable and Hashable 推出以前,做不了什麼。
Matt Diephouse 在提案中解釋了為什麼讓這個令人費解的型別去遵守 Equatable
和其他協議:
Never
在表示不可能執行的程式碼方面非常有用。大部分人熟悉它,是因為它是fatalError
等方法的返回值,但Never
在泛型方面也非常有用。比如說,一個Result
型別可能使用Never
作為它的Value
,表示某種東西一直是錯誤的,或者使用Never
作為它的Error
,表示某種東西一直不是錯誤的。
Swift 沒有標準的 Result
型別,大部分情況下它們是這個樣子的:
enum Result<Value, Error: Swift.Error> {
case success(Value)
case failure(Error)
}
複製程式碼
Result
型別被用來封裝非同步操作生成的返回值和異常(同步操作可以使用 throw
來返回異常)。
比如說,一個傳送非同步 HTTP 請求的函式可能使用 Result
型別來儲存響應或錯誤:
func fetch(_ request: Request, completion: (Result<Response, Error>) -> Void) {
// ...
}
複製程式碼
呼叫這個方法後,你可以使用 switch
來分別處理它的 .success
和 .failure
:
fetch(request) { result in
switch result {
case .success(let value):
print("Success: (value)")
case .failure(let error):
print("Failure: (error)")
}
}
複製程式碼
現在假設有一個函式會在它的 completion
中永遠返回成功結果:
func alwaysSucceeds(_ completion: (Result<String, Never>) -> Void) {
completion(.success("yes!"))
}
複製程式碼
將 Result
的 Error
型別指定為 Never
後,我們可以使用型別檢測體系來表明失敗是永遠不可能發生的。這樣做的好處在於,你不需要處理 .failure
,Swift 可以推斷出這個 switch
語句已經處理了所有情況。
alwaysSucceeds { (result) in
switch result {
case .success(let string):
print(string)
}
}
複製程式碼
下面這個例子是讓 Never
遵循 Comparable
協議,這段程式碼把 Never
用到了極致:
extension Never: Comparable {
public static func < (lhs: Never, rhs: Never) -> Bool {
switch (lhs, rhs) {}
}
}
複製程式碼
因為 Never
是一個無例項型別,所以它沒有任何可能的值。所以當我們使用 switch
遍歷它的 lhs
和 rhs
時,Swift 可以確定所有的可能性都遍歷了。既然所有的可能性 — 實際上這裡不存在任何值 — 都返回了 Bool
,那麼這個方法就可以正常編譯。
工整!
使用 Never
作為兜底型別
實際上,關於 Never
的 Swift Evolution 提案中已經暗示了這個型別在未來可能有更多用處:
一個無例項型別可以作為其他任意型別的子型別 — 如果某個表示式根本不可能產生任何結果,那麼我們就不需要關心這個表示式的型別到底是什麼。如果編譯器支援這一特性,就可以實現很多有用的功能……
解包或者死亡
強制解包操作(!
)是 Swift 中最具爭議的部分之一。(在程式碼中使用這個操作符)往好了說,是有意為之(在異常時故意讓程式崩潰);往壞了說,可能表示使用者沒有認真思考。在缺乏其他資訊的情況下,很難看出這兩者的區別。
比如,下面的程式碼假定陣列一定不為空,
let array: [Int]
let firstIem = array.first!
複製程式碼
為了避免強制解包,你可以使用帶條件賦值的 guard
語句:
let array: [Int]
guard let firstItem = array.first else {
fatalError("array cannot be empty")
}
複製程式碼
未來,如果 Never
成為兜底型別,它就可以用在 nil-coalescing operator
表示式的右邊。
// 未來的 Swift 寫法? ?
let firstItem = array.first ?? fatalError("array cannot be empty")
複製程式碼
如果你想現在就使用這種模式,可以手動過載 ??
運算子(但是……):
func ?? <T>(lhs: T?, rhs: @autoclosure () -> Never) -> T {
switch lhs {
case let value?:
return value
case nil:
rhs()
}
}
複製程式碼
在拒絕 SE-0217: Introducing the !! “Unwrap or Die” operator to the Swift Standard Library 的原因說明中, Joe Groff 提到,“我們發現過載 [?? for Never] 會對型別檢測的效能產生難以接受的影響”。所以,不建議你在自己的程式碼中新增上面的程式碼。
表示式風格的 Throw
類似的,如果 throw
可以從語句變成一個返回 Never
的表示式,你就可以在 ??
右邊使用 throw
。
// 未來的 Swift 寫法? ?
let firstItem = array.first ?? throw Error.empty
複製程式碼
帶型別的 Throw
繼續研究下去:如果函式宣告的 throw
關鍵字支援型別約束,那麼 Never
可以用來表明某個函式絕對不會丟擲異常(類似於在上面的 Result
例子):
// 未來的 Swift 寫法? ?
func neverThrows() throws<Never> {
// ...
}
neverThrows() // 無需使用 `try` ,因為編譯器保證它一定成功(可能)
複製程式碼
聲稱某個事情永遠不可能發生,就像是向整個宇宙發出邀請,來證明它是錯的一樣。情態邏輯(modal logic)或者信念邏輯(doxastic logic)允許保面子式的妥協(“它當時是對的,或者我是這麼認為的!”),但時態邏輯(temporal logic)似乎將這個約定提到了更高的一個標準。
幸運的是,得益於最不像型別的 Never
,Swift 到達了這個高標準。