Eric Aya
Eric Aya

Reputation: 70113

Can I use `inout` with protocol extensions?

I have a protocol and its extension, and a class conforming to the protocol.

protocol WarAbilities {
    var strength: Int { get set }
    func attack(inout opponent: WarAbilities)
}

extension WarAbilities {
    func attack(inout opponent: WarAbilities) {
        opponent.strength -= 1
    }
}

class Warrior: WarAbilities {
    var strength: Int

    init(strength: Int) {
        self.strength = strength
    }
}

Now if I want to make two warriors fight:

let thug1 = Warrior(strength: 10)
let thug2 = Warrior(strength: 30)

thug1.attack(&thug2)

I get this error message:

error: cannot pass immutable value of type 'WarAbilities' as inout argument

Adding mutating looked promising:

protocol WarAbilities {
    var strength: Int { get set }
    mutating func attack(inout opponent: WarAbilities)
}

extension WarAbilities {
    mutating func attack(inout opponent: WarAbilities) {
        opponent.strength -= 1
    }
}

But the compiler isn't happy either and I fail to understand what the new error message means:

error: cannot pass immutable value as inout argument: implicit conversion from 'Warrior' to 'WarAbilities' requires a temporary

Since Warrior conforms to WarAbilities I thought one of those would work - but it looks like Swift doesn't have this kind of... covariance? I'm not even sure what I'm talking about here.

What's my mistake?

Upvotes: 2

Views: 883

Answers (3)

Sentry.co
Sentry.co

Reputation: 5589

Inout with protocol support:

protocol IPositional{
    func setPosition(position:CGPoint)
}
extension IPositional{
    var positional:IPositional {get{return self as IPositional}set{}}
}
class A:IPositional{
    var position:CGPoint = CGPoint()
    func setPosition(position:CGPoint){
        self.position = position
    }
}
func test(inout positional:IPositional){
    positional.setPosition(CGPointMake(10,10))
}
var a = A()
test(&a.positional)
a.position//(10.0, 10.0)

Conclusion:
The benefit of doing it this way: Is that you can now have one "inout method" for all classes that implements IPositional

Upvotes: 0

luk2302
luk2302

Reputation: 57164

Two things are wrong in my opinion:

  1. passing a let-constant to inout
  2. passing a subtype as inout

Note that in your example you do not even need inout since you do not overwrite the parameter, but some instance member of that parameter. Simply dropping the inout will probably work in your case since you do not need it at all. (see matt's answer for proper dealing with this issue)

Note secondly that using mutating probably is not going to do you any good here since that only is talking about mutating its own members, not the members of the parameters.


Explanation of the problem:
Lets consider the far simpler problem which produces the exact same problem output:

protocol A {}
class B : A {}

func c(inout d : A) {}

let a = B()
c(&a)

That firstly does not work because a is a constant. Therefore change the let to var.
Then the compiler complains about requiring a temporary. That is a quite confusing error message but the problem actually makes sense.

You cannot pass a subtype of some Type to an inout or an implementation of some protocol to an inout expecting the protocol

Explanation - consider the following example:

class A {}
class B : A {}

func c(inout a : A) { a = A() }

What should happen if you call that method via

var b = B()
c(&b)

b would be changed and it would now be the same as a - but a is not of type B which b is said to be.

Note that the following will work again:

var b : A = B()
c(&b)

We pass in a B but actually only worry about it being an A. Same goes for protocols:

protocol P {}
class K : P {}
class X : P {}

func f(inout p : P) { p = X() }

var k = K()
f(&k) // does not work

var p : P = K()
f(&p) // works

Upvotes: 2

matt
matt

Reputation: 535606

Make it a class protocol and get rid of the (then) unnecessary inout stuff:

protocol WarAbilities : class {
    var strength: Int { get set }
    func attack(opponent: WarAbilities)
}

extension WarAbilities {
    func attack(opponent: WarAbilities) {
        opponent.strength -= 1
    }
}

class Warrior: WarAbilities {
    var strength: Int

    init(strength: Int) {
        self.strength = strength
    }
}

let thug1 = Warrior(strength: 10)
let thug2 = Warrior(strength: 30)

thug1.attack(thug2)

thug2.strength // 29

(Indeed it's rather unclear to me why you need a protocol here at all; since Warrior is a class, you can just make WarAbilities its superclass.)

Upvotes: 4

Related Questions