Another example on conflicting requirements in protocols

Just FYI. This is another example that Swift don't detect conflicting requirements in protocols until the problematic method is actually called (the issue is caught at compilation time).

The code contains two protocols. The first is a feature, the second is a product with that feature. The first protocol:

protocol FeatureX {
    var x: Int { get set }
    mutating func modifyX(x: Int)
}

extension FeatureX {
    mutating func modifyX(x: Int) {
        self.x = x
    }
}

The second protocol:

protocol WithFeatureX {
    var featureX: FeatureX { get }
}

Note that the definition of the second protocol is buggy: the featureX property should be settable, otherwise modifyX() would fail. However, the compiler doesn't emit error until the method is called on concrete types.

struct FeatureXImpl: FeatureX {
    var x: Int
    var y: Int
}

struct Product: WithFeatureX {
    var featureImpl = FeatureXImpl(x: 1, y: 1)

    var featureX: FeatureX { featureImpl }
}

let p = Product()
print(p.featureX.x)
p.featureX.modifyX(x: 10) 
// error: cannot use mutating member on immutable value: 'featureX' is a get-only property

If I remove the p.featureX.modifyX(x: 10) line, the code just works fine.

I don't think the WithFeatureX is exactly buggy. It could very well be used to move featureX around (without calling any of its mutating functions). It could also be used to take a snapshot of featureX at the time it is created. It may very well be intentional to forbid any uses of mutating function on featureX until a later time, when the value is assigned to some other places.

1 Like

There is nothing here for the compiler to catch. You can still use the mutating method if you assign featureX to a variable:

let p = Product()
var f = p.featureX
f.modifyX(x: 10)
1 Like

Thank @Lantua and @hisekaldma for your comments. Now that you said it, I realize the code below has the same behavior and it's quite typical code.

struct Struct {
    var x: Int
    
    mutating func modifyX(x: Int) {
        self.x = x
    }
}

let s = Struct(x: 1)

I think my confusion was because I thought when the compiler checked code validity, it always assumed the worst case. I still think that's true, but in the above example, I guess what happens is that the compiler doesn't do mutability check when user declares a variable or a property. It only do mutability check when the code actually mutates a variable or a property.

Also, I perhaps expected too much from protocols. I consider protocol as a language to do design and hope the compiler to catch design issues. But in the above case, the "wrong" definition of WithFeatureX is also a valid definition, so there is no way for compiler to tell it.

1 Like
Terms of Service

Privacy Policy

Cookie Policy