Error "Inout argument could be set to a value with a type other than ..." when passing in a protocol type

257 Views Asked by At

I am coming from C++ to Swift. I have this situation with respect to protocols and structs (I am working with random numbers):
(1) A protocol RandomPr that specifies there are methods such as randFloat() that returns a Float between 0 and 1.
(2) A struct RandomS that implements RandomPr using a "real" random number generator.
(3) A struct FakeRandomS that implements RandomPr, but has additional methods such as loadFloat() to load an array of Floats that are then regurgitated when I call randFloat() on a FakeRandomS instance. (For testing purposes.)

Now I have a function DoSomething(rng: inout RandomPr), which I want to use with both RandomS and FakeRandomS. The parameter has to be in-out as I need to update the RNG (whether it is real or fake). No problem calling DoSomething with an instance of RandomS.

But if I do

var fakeRng = FakeRandomS()
fakeRng.loadFloat([0.1, 0.2, 0.3])
DoSomething(rng: &fakeRng)

I get an error "Inout argument could be set to a value with a type other than 'FakeRandomS'. The suggested fix is to define fakeRng as

var fakeRng: RandomPr = FakeRandomS()

But now trying to call loadFloat on fakeRng fails because RandomPr doesn't have a method loadFloat, and the compiler doesn't see that fakeRng does have one.

I tried making another protocol FakeRandomPr that contains the extra methods that FakeRandomS has, and defining

var fakeRng: RandomPr & FakeRandomPr = FakeRandomS()

but, frustratingly, I now get the "Inout argument could be set to a value with a type other than ..." error back again.

I could do the equivalent of this in C++ without problems (a pure abstract base class and two child classes, passed in by reference), and it didn't matter that one of the things I wanted to pass in has extra methods. I want to translate this into protocols and structs. What is the Swift solution?

2

There are 2 best solutions below

2
OOPer On

What is the Swift solution?

You may need to use generics:

func doSomething<RP: RandomPr>(rng: inout RP) {
    //...
}

Swift is not just another syntax of C++, better think in Swifty way.

0
Gordon Monro On

OK - I posted this a while ago. Now I have come back to it, and obtained some understanding, thanks to the answer by Hamish in Why do we need a generic here? Isn't the protocol enough?.

I did not realise that with an inout parameter with type given as a protocol, the whole input object could be replaced by something with a completely different type (in Hamish's example an object of type struct Foo is replaced by an object of type struct Bar). If I then make subsequent use of the object, I can't assume anything about it except that it satisfies the protocol. The compiler tries to enforce this, which is why it objected to additional methods on one of the structs.

Belated thanks to Hamish.