Protocol inheritance/composition ignores shared base protocol

Hi, 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 :)

1 Like

Generally an implementation isn’t allowed to be declared as giving a subtype of the protocol’s declaration, even if it would be safe to do so. The simplest workaround is to have a separate stored property of the actual type, and then a getter to satisfy the protocol requirement.

2 Likes

Alternatively, don't use existentials.

protocol SomeFunctionality {
  associatedtype Thing: BaseProtocol
  var thing: Thing { get }
}

protocol WidelyUsedProtocol: SomeFunctionality { }
protocol BaseProtocol { }
protocol SomeExtraProtocol: BaseProtocol { }

final class RealImplmentation<Thing: SomeExtraProtocol>: WidelyUsedProtocol {
  var thing: Thing
  init(thing: Thing) { self.thing = thing }
}

Previous discussion: Using subtypes in the implementation of protocol properties and function return values

5 Likes

using associated types seems like a very elegant solution, thats some interesting input that I'll be trying over the weekend
thanks a lot!

although I don't understand why we can't have subtypes of protocols, in this simple case it's provable to be safe?

It wouldn’t be too difficult to implement, the main design question to solve is how to do this without introducing ambiguity in existing code. Also associated type inference relies on exact matching of types so it would interact poorly this capability, and we would need to specify under what circumstances it shouldn’t be attempted at all.

A more exotic future direction that would solve this is some form of subtype requirement, so a protocol could declare an associated type and state that it must be a subtype of an any P. But that’s more hand-wavy on my part. ;-)

1 Like