为什么nil-coalescing运算符返回可选的?

时间:2019-12-11 16:51:56

标签: swift optional nil-coalescing-operator

我在链中使用 Swift 的nil-coalescing运算符来打印三个值之一。前两个是可选的,最后一个是非可选的:

let optionalString: String? = nil
let optionalError: Error? = MyError.anError
let defaultString: String = "default"
print(optionalString ?? (optionalError ?? defaultString))

我很惊讶看到这段代码打印出来

  

Optional(MyError.anError)

到控制台。我期待一个非可选的项目:

  

MyError.anError

为什么这是可选的?

错误定义如下:

enum MyError: Error {
    case anError
}

我知道nil-coalescing运算符有两种实现方式:

  1. func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T

  1. func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T?) rethrows -> T?

因此,??应该返回可选的(T?仅当defaultValue也是可选的(T? 。但是事实并非如此:

第一个??运算符的默认值为(optionalError ?? defaultString)。要评估此值的类型,我们需要评估第二个??运算符的结果。由于defaultString的类型为String,因此是非可选的,因此函数(1)用于nil-coalescing运算符。因此,(optionalError ?? defaultString)的结果也必须是非可选的,这是第一个??运算符的默认值。同样,函数(1)用于nil-coalescing运算符,因此optionalString ?? (optionalError ?? defaultString) 应该返回* non- *可选。

显然,我的逻辑存在缺陷,但我看不到。有什么解释吗?

1 个答案:

答案 0 :(得分:1)

免责声明:我不确定是否应该将其添加为问题的补充或是否应将其发布为答案。我决定为后者。这不是防弹的解释,但我认为对于许多可能遇到相同问题的人来说,它“足够接近”。


出了什么问题:大局观

以下两条注释指出所提供的代码无法为注释程序编译,这将我推向正确的方向:

我将代码复制到一个空白的操场上并运行它。对我来说,它是 did 编译的,但是它还显示了一个警告消息,它在之前没有显示(由于某些原因,原始项目中的自动完成和编译器警告已损坏)

Playground screenshot

警告

  

表达从“任何吗?”隐式强制到“任何”

告诉我表达式(optionalError ?? defaultString)显然被评估为类型Any?,这是可选的。然后,编译器选择??运算符(1)来评估整个表达式,这要求其默认值(optionalError ?? defaultString)是非可选的。因此,该值从Any?隐式强制为Any。这就是打印Optional(MyError.anError)的原因。


为什么会出错?

还有两个问题:

  1. 为什么编译器选择运算符(1)?
  2. 为什么(optionalError ?? defaultString)被评估为Any?

我没有问题1

的答案

但是我的猜测是,当参数类型不匹配并且不清楚要使用哪个函数时,编译器会简单地为函数(1)分配更高的优先级。 (我很高兴得到纠正。)

关于问题2

optionalError的类型为MyError?,而defaultString的类型为String??运算符的签名为:

  1. func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T
  2. func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T?) rethrows -> T?

因此,optionaldefaultValue这两个参数必须具有“相同”的泛型类型(仅在第一种情况下,一个参数是该特定类型的可选参数,而另一个则不是) 。 MyErrorString显然不是同一类型。因此,为了使其与函数签名的条件匹配,编译器必须将两种类型都强制转换为Any

因此,MyError?被强制转换为Any?,显然,String也被强制转换为Any?,因此,使用函数(2)进行求值(optionalError ?? defaultString )to好吗?

这又引发了一个问题,为什么在这种情况下使用函数(2),这与我对问题(1)的假设相矛盾,但是我的主要结论是,使用两个函数简直是一个坏主意nil-coalescing运算符设置不同的类型。


背景:

我从RxSwift的一本教科书中获得了此代码示例,该示例略有不同,因为它使用的是必须符合CustomStringConvertible的通用类型:

func print<T: CustomStringConvertible>(label: String, event: Event<T>) {
    print(label, event.element ?? event.error ?? event)
}

首先让我尝试一下的是,当我从书中运行代码时,它打印了一个可选内容,而书中声称它将打印一个非可选内容。