Never

原文连接:swift.gg/2018/08/30/…
做者:Matt
译者:雨谨
校对:numbbbbb,wongzigii,Firecrest
定稿:CMB
git

Never 是一个约定,表示一件事在过去或将来的任什么时候段都不会发生。它是时间轴上的一种逻辑上的不可能,在任何方向延展开去都没有可能。这就是为何在代码中看到 这样的注释 会特别让人不安。github

// 这个不会发生
复制代码

全部编译器的教科书都会告诉你,这样一句注释不能也不会对编译出的代码产生任何影响。墨菲定理 告诉你并不是如此,注释如下的代码必定会被触发。swift

那 Swift 是如何在这种没法预测的、混乱的开发过程当中保证安全呢?答案难以置信:“什么都不作”,以及“崩溃”。数组


使用 Never 替换 @noreturn 修饰符,是由 Joe GroffSE-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!"))
}
复制代码

ResultError 类型指定为 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 遍历它的 lhsrhs 时,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 到达了这个高标准。

本站公众号
   欢迎关注本站公众号,获取更多信息