Swift5.7 支持结构化不透明结果类型

2022-07-04 11:36:58 浏览数 (2)

介绍

当前提议主要是讲苹果在 Swift5.7 支持不透明结果类型的结构化表达,目前在 Swift5.7 已经实现。

不透明结果类型可以用作函数的结果类型,变量的类型和下标元素的结果类型。在这三种情况下,不透明结果类型必须是整个类型。比如用于函数的整个返回结果类型。本篇提议建议取消这种限制,并允许在“结构”位置使用不透明的结果类型。

目的

当前语法中对不透明结果类型的限制阻止了它们在许多常见的 API 模式中使用。可以看下面四个常见的例子:

代码语言:Swift复制
// ❌,函数的不透明结果返回值有可能失败
func f0() -> (some P)?  {  /* ... */ }

// ❌,不能把不透明结果类型作为多个返回值中的一个,必须是对应单个且整个返回值
func f1() -> (some P, some Q)  {  /* ... */ }

// ❌,不能返回一个懒加载的计算 some 类型。(当 f2 调用完成且返回结果时,返回类型是`() -> some P`,此时返回值中并不确定 some 类型)
func f2() -> () -> some P  {  /* ... */ }

// ❌,不能把不透明结果类型嵌入到更大的结构中
func f3() -> S<some P>  {  /* ... */ }

上面四个调用示例都是之前的语法约定,如果解除这些限制,就可以使用不透明结果类型来表达更多 API 模式。所以我们应该允许在函数的结果类型、下标元素的类型和变量的类型,这三种类型的结构位置中使用不透明结果类型。

详细设计实现

可选语法

不透明结果类型的可选必须使用(some P)?表示,一个已经解包的不透明结果类型的可选必须使用(some P)! 表示。

为什么不用 some P?some P! 呢?some P?这种表达会被解释为some Optional<P>,由于不透明类型一定是Any, AnyObject, 组合的协议,或者基类中的一种,所以some P?这种表达一定错误。some P!也是同样的道理。

高阶函数

如果函数的结果类型、下标的结果类型和变量的类型是函数类型,那么该函数类型只能在返回位置包含结构不透明类型。例如,func f() -> () -> some P合法,但是func g() -> (some P) -> ()会直接报错,这里主要因为some不能出现在函数结果类型的参数位置:

代码语言:Swift复制
protocol P {}
func g() -> (some P) -> () {  ...  } // 'some' 不能出现在 '(some P) -> ()' 的参数位置
约束推断能力

当泛型参数类型用在函数签名(可以简单理解为函数名加参数的唯一标识)的结构位置时,编译器会根据使用泛型参数的上下文来隐式约束泛型参数类型。例如下面例子中f函数中泛型参数会被推断为Hashable

代码语言:Swift复制
struct H<T: Hashable> {  init(_ t: T) { } }
struct S<T>{  init(_ t: T) { }  }

// 与 'f<T: Hashable>' 等价,因为返回值 'H<T>' 就是指 'T: Hashable'
func f<T>(_ t: T) -> H<T> {
    var h = Hasher()
    h.combine(t)        // 可以编译通过, 这里推断知道 'T: Hashable'(combine 是 Hashble 的实例方法)
    let _ = h.finalize()
    return H(0)
}

// 'S<T>' 没有实现任何 'T' 相关的协议
func g<T>(_ t: T) -> S<T> {
    var h = Hasher()
    h.combine(t) // ❌ERROR - 'combine' 是 'Hashable'的实例方法,调用者泛型 'T'' 实必须实现 'Hashable' 协议
    let _ = h.finalize()
    return S(0)
}

但不透明结果类型没有类型推断的特性,例如把f函数的返回值使用不透明结果类型H<some P>表示,由于some没有类型推断能力,T无法根据上下文推断是否遵守Hashable,此时f函数会直接报错。例如:

代码语言:Swift复制
// ❌,类型 'some P' 没有遵守协议 'Hashable'
func f<T>(_ t: T) -> H<some P> {  /* ... */  }

对源代码兼容性的影响

新增特性,没有兼容性影响。

跟 SE-0244 中讨论的一样:

如果在库中采用不透明类型,则一开始会破坏源代码[...]兼容性, 因为不支持可变参数。但是由于向客户端暴露的细节很少,实际上可以对源代码和 ABI 稳定性来说长期受益。对源代码兼容性也有一些缓解措施,比如,原类型的弃用周期更长,或者用新的函数签名(返回的不透明结果类型)重载旧的函数签名(返回命名类型)。

对 ABI 稳定性的影响

新增特性,对 ABI 无影响。

对 API 扩展性的影响

新增特性,没有扩展性相关的影响。 SE-0244 提议已经说明:

不透明结果类型是函数的结果类型,变量类型,下标的元素类型,这三种类型的一部分。在不破坏API/ABI 稳定性的前提下,无法更改不透明结果类型的要求。但是,底层的具体类型可以在不破坏 ABI 的情况下从一个版本更改到下一个版本,因为 API 上层并不知道底层的具体类型。

Rust 的Impl Trait特性

Swift 中的不透明结果类型是受 Rust 中的impl Trait特性启发而来。SE-0244 中对比了someimpl Trait的异同点。其中一个不同点是impl Trait允许在结构位置使用,这个特性与当前提议基本相同。impl Trait与当前提议特性有个不同点,是impl Trait不会出现在闭包特性或者函数指针的返回类型中。

总结

通过当前提议 SE-0328,你应当知道:

  • 结果类型支持(some P)?(some P)!
  • 返回结果类型是函数类型时,支持() -> (some P)

0 人点赞