Inout argument could be set to a value with a type other than 'CHILD', use a value declared as type 'PARENT' instead

Viewed 633

I am trying to design an abstract class in swift, within which a function takes an instance of a child of that class. However, I want to specify the type of the argument as the name of the abstract class, since I don't know which child of the class would be passed. Example code:

protocol Character {
...
}
extension Character {
...
   mutating func fight(target: inout Character) {
      target.hp -= 10
   }
}
struct Warrior: Character {
...
}
struct Mage: Character {
...
}
foo = Warrior()
bar = Mage()
foo.fight(target: bar)

Here in this code I get the error: Inout argument could be set to a value with a type other than 'Mage', use a value declared as type 'Character' instead, meaning the compiler is not able to regocnize that Mage implements Character and therefore is of type character. How do I solve this?

2 Answers

I'm assuming your code looks something like this:

extension Character {
    mutating func fight(target: inout Character) {
        target.hp -= 10
    }
}

var foo = Warrior()
var bar = Mage()

foo.fight(target: &bar)

The problem is that your bar variable is inferred to be of type Mage which you can't pass as an inout argument to a function that takes Character.

To understand why this limitation exists, imagine that your fight function had this implementation:

mutating func fight(target: inout Character) {
    target = Warrior()
}

If passing a variable of type Mage to that function was valid then you would be assigning a value of type Warrior to a variable of type Mage which obviously can't work.

If you want to pass bar to your function then you would need to declare it as:

var bar: Character = Mage()

Another way to do it would be to make your fight function generic which would work with your original calling code but would have other limitations:

mutating func fight<T: Character>(target: inout T) {
    target.hp -= 10
}

Elaborating on @dan's suggestion to use generics, I tried this successfully in an Xcode 11.5 playground

protocol Character {
    var hp: Int { get set }
}

extension Character {
    mutating func fight<T: Character>(target: inout T) {
        target.hp -= 10
    }
}

struct Warrior: Character {
    var hp = 40
}

struct Mage: Character {
    var hp = 20
}

var foo = Warrior()
var bar = Mage()

foo.fight(target: &bar)
print(bar.hp) // 10

Related