SE-0360, Swift 5.7 已实现
自 SE-0244 引入以来,不透明的结果类型已成为类型级抽象的强大工具,允许库作者隐藏其API的实现细节。
根据 SE-0244 中描述的规则,返回不透明结果类型的函数必须从每个返回语句中返回与T
类型相同的值,并且T
必须满足不透明类型上所述的所有约束。
当前模型和实现限制了不透明结果类型作为抽象机制的有用性,因为它阻止了框架引入新类型并将其用作现有API 的基础类型。为了弥补这一可用性差距,本篇提议建议在可用条件下放宽对返回的同类型限制。
为了说明在不透明结果类型和可用性条件之间的交互问题,我们列举个框架例子,该框架下定义Shape
协议,并且Square
类型已经遵循该Shape
协议,如下:
protocol Shape {
func draw(to: Surface)
}
struct Square : Shape {
...
}
在该库的新版本中,库作者决定引入一个新的shape
- Rectangle
, 但是新类型被限制了可用性,在新的版本需要通过@available
来区分,例如:
@available(macOS 100, *)
struct Rectangle : Shape {
...
}
因为Rectangle
是Square
的变体,那么Square
是可以转化为Rectangle
。但是这种转化还是需要通过@available
限制可用性,原因是因为Rectangle
类型已经是受限制可用的。
@available(macOS 100, *)
extension Square {
func asRectangle() -> some Shape {
return Rectangle(...)
}
}
新方法asRectangle()
必须在可用性上下文中声明才能返回Rectangle
, 这限制了它的可用性,因为asRectangle()
的所有用途都封装在#avaliable
代码块中。
如果框架的老版本就存在asRectangle()
,不声明if #available
条件,就无法使用新类型:
struct Square {
func asRectangle() -> some Shape {
if #available(macOS 100, *) {
return Rectangle(...)
}
return self
}
}
但上述这样声明是不允许的,因为asRectangle()
函数主体中的所有返回语句都必须返回相同的具体类型。所以上述代码会报错:
error: function declares an opaque return type 'some Shape', but the return statements in its body do not have matching underlying types
func asRectangle() -> some Shape {
^ ~~~~~~~~~~
note: return statement has underlying type 'Rectangle'
return Rectangle()
^
note: return statement has underlying type 'Square'
return Square()
^
对于库作者来说,这是一个死胡同,尽管SE-0244指出,在未来版本的库/框架中可能更改基础结果类型,但这个假设是基于该类型已经存在,因此可以在所有返回语句中使用。恰巧在实际情况下,新类型往往是后续版本中新增,所以所绕进了一个死胡同。
为了弥补上述可用性不足,本篇提议:放宽带有if #available
函数的同类型返回限制,如果if #available
条件一定会被执行,那么它可以返回与函数其余部分返回类型不同的类型。不要求必须返回同一类型。
这个提议给函数带来 2 点改变:
if #available
可以根据它的动态性返回不同类型由于函数中的返回类型在函数未运行时就要确认,即在声明函数时就要确认函数的返回类型,但如果把可用性条件判断(if #available
)和其他条件判断(比如,guard, if, switch)混合在一起,函数就无法确定返回类型, 并且需要在if #available
中返回与函数其余部分相同的类型。(意思是:在if #available
内部包含的所有if
, guard
, switch
的每个return
分支上,都需要返回相同类型)下面这个例子满足这些规则:
func test() -> some Shape {
if #available(macOS 100, *) { ✅
return Rectangle()
}
return self
}
无条件可用子句称为
if #available
子句。
if #available
语句是满足下列条件的if
或者else if
语句:
if
语句的一部分if
条件语句之前,其所在函数还没有出现return
语句#available
条件if
子句,要么是紧随无条件可用性子句之后的其他if
子句return
语句return
或者throw
结束 if #available
子句以外的所有返回语句必须返回彼此相同的类型,并且该类型必须与其所在函数一样可用。
所有在给定的if #available
子句内的return
语句必须每次返回相同的类型,这种类型必须与子句的#available
条件一样可用。并且这个类型不必与子句之外的其他任何返回类型相同。
在所在函数中,必须至少有一种return
语句。如果在无条件可用性子句之外,没有return
语句,那么在if #available
子句内部,至少存在一种返回类型与函数的返回返回相同。
函数的返回类型一般是:
if #available
子句的return
语句返回类型;if #available
子句之外的return
语句返回类型;if #available
子句的return
语句的返回类型,这个类型与函数返回类型一样第一个示例是正确的,因为第一个if #available
和第二个if #available
都以return
结束,代码如下:
func test() -> some Shape {
if #available(macOS 100, *) { ✅
return Rectangle()
} else if #available(macOS 99, *) { ✅
return Square()
}
return self
}
但是如果把上述条件修改下:
func test() -> some Shape {
if cond {
...
} else if #available(macOS 100, *) { ❌
return Rectangle()
}
return self
}
编译器此时会报错,因为if #available
和动态条件相关联。
如果if #available
在可以return
的动态条件之后,这种情况也是不允许的。比如在函数开始时,提前 guard
并返回对象,那么这属于一个动态条件, 这种情况也会报错:
func test() -> some Shape {
guard let x = <opt-value> else {
return ...
}
if #available(macOS 100, *) { ❌
return Rectangle()
}
return self
}
同样,如果if #available
出现在某个循环内,也会报错:
func test() -> some Shape {
for ... {
if #available(macOS 100, *) { ❌
return Rectangle()
}
}
return self
}
下面例子中的 test()
方法符合上述规则,会通过编译器编译。该方法中if
条件在它的分支内都有结果,且返回结果类型相同。if #available
始终会通过return
结果分支.
func test() -> some Shape {
if #available(macOS 100, *) {
if cond { ✅
return Rectangle(...)
} else {
return Rectangle(...)
}
}
return self
}
但是如果test()
函数中if #available
内if
分支返回不同类型,则编译器无法通过:
func test() -> some Shape {
if #available(macOS 100, *) {
if cond { ❌
return Rectangle()
} else {
return Square()
}
}
return self
}
本篇提议的这种语义调整非常适合现有模型,因为它确保每个平台始终有一个通用的基础类型。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。