Protocol typed property in protocols

Currently the following piece of code does not compile :

protocol P {}
struct A: P {}
protocol Q {
    var p: P { get }
}
struct B: Q {
    var p = A()
}

The compiler raise the error: Type 'B' does not conform to protocol 'Q'
One way to workAround this problem is to declare a private variable of type A and for the variable p to dynamically return a

struct B: Q {
    private var a = A()
    var p: P { return a }
}

It would be awesome if the compiler could recognize A implements P and recognize the first writting.

1 Like

Explicitly writing the type var p: P = A() will do for now.

1 Like

It would enable to satisfy the compiler but it would not enable to use p as an instance of type A
It can of course be used by casting to A each time we need it, but it would be great if the compiler handle all this by unederstanding A is implementing P

That is because you specified P as the concrete type of the variable. Writing

protocol P { ... }
struct Foo: P { ... }

protocol P1 {
    var foo: P { get }
}

doesn't mean you can conform to P1 like this:

class Foo1: P1 {
    var foo: AnyTypeConformingToP = ...
}

since var p isn't generic. It's of type P.

P.S.

if you want to do it that way, you have to go "generic":

protocol P {}
struct A: P {}

protocol Q {
    associatedtype Type1: P
    
    var p: Type1 { get }
}

struct B: Q {
    typealias Type1 = A
    
    var p = A() // no problem
}
2 Likes

Maybe I am missing the obvious but what prevents the compiler to understand A is implmenting P kind of like polymorphism.

Your solution using protocol with associated type would work but defeat the purpose of anonymising B with a protocol since B cannot be casted as Q which "Can only be used a type constraint"
It could be stored using type erasure but we would have to specify the type of A and thus the variable could not be anonymised by the protocol P.
Maybe at this point there is another work around to anonymise the type A but it's getting awfully complicated for something that should be that much don't you agree?

If you're asking why the compiler doesn't infer var p = A() as P, it simply isn't smart enough.

You can anonymize B if you don't need to store it as Q.

Because Q declares “Any type conforming to me, will have a property p whose type is an existential that can store any value conforming to P.”

Suppose we make a protocol R that refines Q to require p is mutable:

protocol R: Q {
  var p: P { get set }
}

Then we can write an extension method on R that sets p to something:

struct C: P {}

extension R {
  mutating func foo() {
    p = C()
  }
}

And now, to bring it all together, we can retroactively conform B to R:

extension B: R {}

This all works exactly as it says, and it means we can set the value of B.p to an instance of C.

1 Like

It much clearer to me why it's not possible.
Thanks a lot for taking the time of explaining it to me.

This is a reasonable in request. In general, it makes sense for the witness to be a subtype of the protocol requirement. For example, if the protocol requires a method returning Any, it makes sense for the conforming type to provide a method returning Int, since every method returning Int is also a method returning Any, etc.

The canonical JIRA bug for this is [SR-522] Protocol funcs cannot have covariant returns · Issue #43139 · apple/swift · GitHub. A contributor started working on implementing this, but unfortunately it was abandoned: https://github.com/apple/swift/pull/8718

If someone is interested in dusting this off though, it should probably go through evolution discussion, and would need to be gated on -swift-version <N> since it's source breaking. There are also some subtle issues to resolve, for example if multiple witnesses match a single requirement, how do you rank them? Or what if a default in an extension matches exactly, but another method in the type itself is an inexact match?

Slava

If B defines a property whose type is a subtype of P, then B cannot conform to R, because the property requirement is mutable. However for an immutable property requirement there is no such difficulty, and the conformance could be allowed.

2 Likes

It does make sense, but it violates Liskov substitution.

protocol P {
    var foo: UIView { get set }
}

class A: P {
    var foo: UIButton = UIButton()
}

let a = A()
var p: P = a
p.foo = UIView() // or UIDatePickerView for instance

a.foo.(...) ?

It's fine if it's a get-only though ;)

4 Likes

If the property is settable, sure. If the property requirement in the protocol is read-only, its equivalent to a method returning a value of that type.

Hello @Slava_Pestov I would like to give it a try but unfortunatelly the closest I've come to C++ is programming in C and I am not really familar with the swift compiler project, however I am quite tenacious, if you could pinpoint me to something that could help me understand how the compiler handle this issue, I will gladly give it a go.