使用协议参数化的通用 Swift class 在运行时失败。为什么?
Generic Swift class parametrized with a protocol fails at runtime. Why?
给定两个 类 A
和 PC
以及协议 P
:
@objc public protocol P {} // fun fact on the side: without @objc, swiftc crashes
public class PC : P {}
public class A<T:P> {
public init() { }
public func test() -> Void {
let pc = PC()
let t = pc as T // this cast leads to the error mentioned below
}
}
这些编译正常。
现在我想这样调用 test()
:
let a = A<P>() // parametrized with the *protocol* P
a.test()
这也编译得很好。
然而,当我 运行 它并尝试转换为 T
(通过 pc as T
)时,调试器登陆:
swift_dynamicCastUnknownClassUnconditional:
[...]
0x105fedf76: leaq 0x3680f(%rip), %rax ; "bad metadata kind!"
为什么会这样?
我假设因为 T
受 P
约束并且 PC
采用 P
,所以每个 PC
也必须是 T
.
附加信息——这个:
let a = A<PC>() // parametrized with the *class*, not the protocol
a.test()
很有魅力。
我并不惊讶这段代码有问题,即使它看起来应该在运行时工作,你可能应该重构它以不同的方式解决问题。这可能值得关注,但我认为这是值得商榷的。归结为我不相信这条线:
let t = pc as T
是正确的做法。 as
表示 "no matter what pc
is, I want to make it of type T
." 但不能保证这是可以的。那么以下情况呢:
extension String: P { }
let x = A<String>()
以上是完全可以接受的声明——String
符合 P
,因此您可以创建一个 A
,其中 T
是 String
。但是你不能写 let pc = PC(); let t = pc as String
– PC
和 String
是完全不相关的类型。如果您尝试在非通用代码中执行此操作,则会出现编译器错误。对于泛型版本,您有点像在阴间——泛型函数应该就像说 "at compile time, assuming T
were replaced with an actual type, write me a function that's just like that"。在那种情况下应该如何解释 as T
真的不清楚——它可能会在编译时失败,也可能不会。
现在你可能会争辩 "it's OK, I'll always make sure any when I call create my class I only do it with types that will work. It'll never crash because I'll never call it with something that will crash"。你的例子就是这种情况。
但这不是 Swift 泛型的运作方式 – Swift 尽最大努力确保泛型内部发生的事情对符合泛型标准的任何类型始终有效。只是因为,以您在这里调用它的方式,它恰好起作用,并且您已经说服 Swift 让它编译并不能使代码有效,因此,您观察到的行为似乎对我来说很合理。
给定两个 类 A
和 PC
以及协议 P
:
@objc public protocol P {} // fun fact on the side: without @objc, swiftc crashes
public class PC : P {}
public class A<T:P> {
public init() { }
public func test() -> Void {
let pc = PC()
let t = pc as T // this cast leads to the error mentioned below
}
}
这些编译正常。
现在我想这样调用 test()
:
let a = A<P>() // parametrized with the *protocol* P
a.test()
这也编译得很好。
然而,当我 运行 它并尝试转换为 T
(通过 pc as T
)时,调试器登陆:
swift_dynamicCastUnknownClassUnconditional:
[...]
0x105fedf76: leaq 0x3680f(%rip), %rax ; "bad metadata kind!"
为什么会这样?
我假设因为 T
受 P
约束并且 PC
采用 P
,所以每个 PC
也必须是 T
.
附加信息——这个:
let a = A<PC>() // parametrized with the *class*, not the protocol
a.test()
很有魅力。
我并不惊讶这段代码有问题,即使它看起来应该在运行时工作,你可能应该重构它以不同的方式解决问题。这可能值得关注,但我认为这是值得商榷的。归结为我不相信这条线:
let t = pc as T
是正确的做法。 as
表示 "no matter what pc
is, I want to make it of type T
." 但不能保证这是可以的。那么以下情况呢:
extension String: P { }
let x = A<String>()
以上是完全可以接受的声明——String
符合 P
,因此您可以创建一个 A
,其中 T
是 String
。但是你不能写 let pc = PC(); let t = pc as String
– PC
和 String
是完全不相关的类型。如果您尝试在非通用代码中执行此操作,则会出现编译器错误。对于泛型版本,您有点像在阴间——泛型函数应该就像说 "at compile time, assuming T
were replaced with an actual type, write me a function that's just like that"。在那种情况下应该如何解释 as T
真的不清楚——它可能会在编译时失败,也可能不会。
现在你可能会争辩 "it's OK, I'll always make sure any when I call create my class I only do it with types that will work. It'll never crash because I'll never call it with something that will crash"。你的例子就是这种情况。
但这不是 Swift 泛型的运作方式 – Swift 尽最大努力确保泛型内部发生的事情对符合泛型标准的任何类型始终有效。只是因为,以您在这里调用它的方式,它恰好起作用,并且您已经说服 Swift 让它编译并不能使代码有效,因此,您观察到的行为似乎对我来说很合理。