试图了解与通用枚举有关的“无法将类型...的值转换为期望的参数类型”错误

时间:2019-03-11 19:35:49

标签: swift generics

我遇到了一些通用编程的怪癖,希望在此方面有所帮助。抱歉,如果已经提出要求,则很难将其放入搜索词中。

给出以下代码:

enum Result<T> {
    case success(T)
    case failure(Error)
}

protocol ObjectProtocol {}
class ObjectClass: ObjectProtocol {}

func describe(result: Result<ObjectProtocol>) -> String {
    return "\(result)"
}


let result = Result.success(ObjectClass())
print(describe(result: result))

在最后一行,我得到了错误:

error: cannot convert value of type 'Result<ObjectClass>' to expected argument type 'Result<ObjectProtocol>'

奇怪的是,如果我用此替换最后两行(删除变量定义并仅传递Result内联),它将正常工作:

print(describe(result: Result.success(ObjectClass())))

为什么一个起作用而另一个不起作用?

编辑:

使函数具有泛型可解决此问题:

func describe<T: ObjectProtocol>(result: Result<T>) -> String {
    return "\(result)"
}

但是,如果将Result传递给另一个枚举,而不是传递给一个函数(可以很容易地使之通用),那么我不确定您将如何解决该问题。例如:

enum Result<T> {
    case success(T)
    case failure(Error)
}

protocol ObjectProtocol {}

class ObjectClass: ObjectProtocol {}

enum RequestStatus {
    case completed(result: Result<ObjectProtocol>)
    case notMadeYet
}

let result = Result.success(ObjectClass())
let status = RequestStatus.completed(result: result)

这会产生与上述相同的错误。

2 个答案:

答案 0 :(得分:0)

类型推断引擎很容易受到影响。要使您的思路与类型和谐,您只需执行以下操作:

let result = Result.success(ObjectClass() as ObjectProtocol)

,以便您完全控制T的含义。它可能令人惊讶地微妙。

这些同样有效:

let foo = ObjectClass()
let result = Result.success(foo as ObjectProtocol)

并且:

let foo = ObjectClass()
let result = Result<ObjectProtocol>.success(foo)

换句话说,不要过多依赖推理引擎。

另一种可能性是完全丢弃泛型类型:

protocol ObjectProtocol { /* What does this really require from an Object, anyway? */ }

class ObjectClass { /* ... */ }

/* For the module that relies on the enum, you can add the compatibility. */
extension ObjectClass: ObjectProtocol { /* Is this a marker type or adding substantial behavior? */ }
extension Array: ObjectProtocol {} /* Add any Swift built-in type with extensions */

/* Note the cleaner syntax. */
enum Result {
    case success(ObjectProtocol)
    case failure(Error)
}

enum RequestStatus {
    case completed(result: Result)
    case notMadeYet
}
let foo = ObjectClass()
let result = Result.success(foo)
/* This might be too flexible though -- depends on your design! */
let anotherResult = Result.success(Array<Int>())
let status = RequestStatus.completed(result: result)
let anotherStatus = RequestStatus.completed(result: anotherResult)
/* anotherStatus & status are of the same type. With templates you could have prevented that. */

最后:

/* Let's give full throttle back to tye type-inferencing engine,
 allowing Generics to assume their viral propagation tendencies. */
enum Result<T> {
    case success(T)
    case failure(Error)
}

enum RequestStatus<T> {
    case completed(result: Result<T>)
    case notMadeYet
}

/* Now this code doesn't change at all, but... */
let foo = ObjectClass()
let result = Result.success(foo)

let anotherResult = Result.success(Array<Int>())
let status = RequestStatus.completed(result: result)
let anotherStatus = RequestStatus.completed(result: anotherResult)
/* ...anotherStatus & status are now incompatible types! */

答案 1 :(得分:0)

很好的问题。您所看到的是,对于简单的事情,例如数组分配和其他泛型,Swift表现出协方差。这些工作:

let result: Result<ObjectProtocol> = Result.success(ObjectClass())
print(describe(result: result))
// SUCCESS: you assigned to a variable

print(describe(result: Result.success(ObjectClass())))
// SUCCESS: you "assigned" to a field

也就是说,您可以将Result<ObjectClass>表达式分配给类型为Result<ObjectProtocol>的变量。

但是函数参数和参数不是协变的;您不能将Result<ObjectClass>传递到类型Result<ObjectProtocol>的参数。这些失败:

let result: Result<ObjectClass> = Result.success(ObjectClass())
print(describe(result: result))
// ERROR: can't pass wrapped class to wrapped protocol

let result = Result.success(ObjectClass()) // inferred class
print(describe(result: result))
// ERROR: can't pass wrapped class to wrapped protocol

通常可以通过以下方法来解决这些问题:让您的函数不采用参数Result<ObjectProtocol>,而是使用任何实现Result的类将参数“ ObjectProtocol”参数化(并使函数通用)我刚刚看到您在修改中发现了!

Here is an article that may be helpful