原文連接:swift.gg/2018/08/30/…
做者:Matt
譯者:雨謹
校對:numbbbbb,wongzigii,Firecrest
定稿:CMB
git
Never
是一個約定,表示一件事在過去或將來的任什麼時候段都不會發生。它是時間軸上的一種邏輯上的不可能,在任何方向延展開去都沒有可能。這就是爲何在代碼中看到 這樣的註釋 會特別讓人不安。github
// 這個不會發生
複製代碼
全部編譯器的教科書都會告訴你,這樣一句註釋不能也不會對編譯出的代碼產生任何影響。墨菲定理 告訴你並不是如此,註釋如下的代碼必定會被觸發。swift
那 Swift 是如何在這種沒法預測的、混亂的開發過程當中保證安全呢?答案難以置信:「什麼都不作」,以及「崩潰」。數組
使用 Never
替換 @noreturn
修飾符,是由 Joe Groff 在 SE-0102: 「Remove @noreturn attribute and introduce an empty Never type」 中提出的。安全
在 Swift 3 以前,那些要中斷執行的函數,好比 fatalError(_:file:line:)
,abort()
和 exit(_:)
,須要使用 @noreturn
修飾符來聲明,這會告訴編譯器,執行完成後不用返回調用這個函數的位置。app
// 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 標準庫普遍使用無實例的枚舉類型來作諸如 定義命名空間 以及 標識類型的含義 之類的事情。fetch
但 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
能夠從語句變成一個返回 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 到達了這個高標準。