问题
I found the following example on the web about using generics together with protocols, however I don't understand why do we need generics at all, when all we need is to use a protocol.
We define a protocol:
protocol Healthy {
mutating func setAlive(status: Bool)
var health: Int { get }
}
And then a function using a generic adotping that protocol
func check<T:Healthy>(inout object: T) {
if (object.health <= 0) {
object.setAlive(false)
}
}
I've changed the code as below and everything is still fine.
func check( object: inout Healthy) {
if (object.health <= 0) {
object.setAlive(status: false)
}
}
Or not?
The only reason I could think of to use a generic there, if it's a protocol has an associatedtype and it can't used as an instance.
回答1:
They express different things. With
func check(object: inout Healthy) {
The object
argument can be any Healthy
conforming instance. Therefore, you could do this:
protocol Healthy {}
struct Foo : Healthy {}
struct Bar : Healthy {}
func check(object: inout Healthy) {
object = Bar()
}
var h: Healthy = Foo()
check(object: &h)
print(h) // Bar()
We called check(object:)
and passed h
(which holds a Foo
instance) as the inout
argument, but ended up with h
holding a Bar
instance.
You'll note that this means we cannot simply call check(object:)
with a concrete-typed inout
argument. The following doesn't compile:
var h = Foo()
// compiler error: Cannot pass immutable value as inout argument:
// implicit conversion from 'Foo' to 'Healthy' requires a temporary
check(object: &h)
Because check(object:)
could assign an arbitrary Healthy
conforming instance to the object
argument, which is not assignable to a Foo
variable.
However, with
func check<T : Healthy>(object: inout T) {
The object
argument is a single specific concrete type that conforms to Healthy
(and this type is satisfied at the call-site). You cannot just assign an arbitrary Healthy
conforming instance to it, as it might not be compatible with the variable type being passed as the inout
argument.
This therefore now allows you to call it with a concrete-typed inout
argument. We can now say:
protocol Healthy {
var alive: Bool { get set }
}
struct Foo : Healthy {
var alive: Bool
}
struct Bar : Healthy {
var alive: Bool
}
func check<T : Healthy>(object: inout T) {
object.alive = false
// illegal
// object = Bar()
}
var h = Foo(alive: true)
check(object: &h)
(note h
is able to be typed as Foo
)
So in most cases, you'll likely want to make the method generic rather than having a protocol-typed inout
parameter, as you'll likely want to be dealing with concrete types.
回答2:
Because this is using an inout
(ugh) and not returning a value means that the generic is not required.
When I would use a generic for this example is if the method signature was like this...
func check<T:Healthy>(object: T) -> T {
}
This would then ensure that the type of object passed in and the type returned are the same type.
Without the generic you could pass in an instance of...
struct A: Healthy {}
and return an instance of...
struct B: Healthy {}
Hmm... maybe this is still the case with the inout
. Can you create another struct that conforms to the protocol and change the object
to an instance of your new struct? Maybe... will have to check that later.
来源:https://stackoverflow.com/questions/44387949/why-do-we-need-a-generic-here-isnt-the-protocol-enough