Search code examples
swiftinheritanceprotocols

Protocol inheritance/composition ignores shared base protocol


this example shows something that hindered me when doing stuff in our corporate code base (100kLoC w/ complicated inheritance, composition cases) My problem is that swift won't accept classes as conformant to protocols if they don't have a property with the exact type but only some subtype

This is a simplified version

// Consider some protocol which adds functionality to some classes
protocol SomeFunctionality {
    var i: Int { get }
    var thing: BaseProtocol { get }
}

// even has a nice method associated wow
extension SomeFunctionality {
    func printI() {
        print(i + thing.i)
    }
}

protocol WideleyUsedProtocol: SomeFunctionality { }

// this is used as the base protocol
protocol BaseProtocol { 
    var i: Int { get }
}

protocol SomeExtraProtocol: BaseProtocol { }

// this is not compilable as it fails with
// RealImplmentation does not conform to SomeFunctionality
// because the compiler misses 
// var thing: any BaseProtocol
final class RealImplmentation: WideleyUsedProtocol {
    var i: Int = 42
    // but var thing: any BaseProtocol is here!
    // SomeExtraProtocol inherits from BaseProtocol
    // this literally is any BaseProtocol
    var thing: SomeExtraProtocol
    
    init(i: Int, thing: SomeExtraProtocol) {
        self.i = i
        self.thing = thing
    }
}

Now my question is, is this some issue with the type system or working as intended? Is there some smart workaround?
I would be very interested in hearing your thoughts and maybe taking over an extension of the type system if something like this is welcome in the language :)


Solution

  • What solved that problem for me are associated types
    Consider the following implementation:

    // [ all protocols etc from before incl. ]
    
    protocol OtherFunctionality {
        associatedtype Thing: BaseProtocol
        var thing: Thing { get }
    }
    
    final class OtherRealImplmentation<Thing: SomeExtraProtocol>: OtherFunctionality {
        var thing: Thing
        
        init(thing: Thing) {
            self.thing = thing
        }
    }
    

    The interesting part here is that the typeclass value of Thing isn't decided until the protocol is adopted (that is in OtherRealImplementation) and is still subject to inheritance which can be used for some cool DRY code wow Here is a link to the relevant swift docs which explain that concept really well