1
votes

I have a protocol with an optional property.

Most of the types that conform to this protocol will have a matching optional property. However, one has a non-optional property of the same type and name.

protocol SomeProtocol {
    var foo: Int? { get }
}

struct StructA: SomeProtocol {
    let foo: Int?
}

struct StructB: SomeProtocol {
    let foo: Int // Type 'StructB' does not conform to protocol 'SomeProtocol'
}

Pressing Xcode's "Fix - Do you want to add protocol stubs?" button adds the optional version of the property, but the structure now has invalid duplicated variable names:

struct StructB: SomeProtocol {
    let foo: Int
    var foo: Int? { return foo } // Invalid redeclaration of 'foo'
}

In the { get }-only case, I had assumed that this would "just work" due to the non-optional always satisfying the constraints of the optional, similar to how you can return a non-optional in a function with an optional return type. But apparently that is not the case.

This works the same for functions as well; a protocol's func bar() -> Int? is not satisfied by a conforming type declaring func bar() -> Int.

Is there any way around this issue? I would prefer not to rename the variables or add intermediate getters.

Has this situation been considered for Swift? What is the rational for not allowing a non-optional to satisfy an optional protocol variable?

2
Protocol is a requirement. If you stated that being optional is a requirement you need to implement it as optional otherwise it won't conform to your protocol.Leo Dabus
If the class/struct that you are implementing that property it will never be nil you can simply force unwrap its value when accessing that propertyLeo Dabus
"I would prefer not to rename the variables or add intermediate getters." You have identified the solution, however. Your other option is to change the protocol. The rationale is that it's not supported. Specifically, Optionals are not treated magically, which they would need to be in order for this to work. (This wouldn't apply to a protocol with a set, and it wouldn't apply to any other generic like Array. So it would be highly magical for Optionals with get. Possible, but compiler-magic required, and it hasn't been common enough for well-designed protocols to add that magic.)Rob Napier
@RobNapier added a workaround that will be sufficient in my case. Also links to a longstanding Swift ticket that would enable this.pkamb

2 Answers

1
votes

The extension-with-default-implementation solution offered by pkamb is helpful in terms of getting the compiler to recognize conformance, but you need to be aware of the strange behavior this can produce.

An object of the newly conforming type will return a different value depending on what type you cast it to (this also includes passing as to a parameter whose type is the protocol):

let bar = StructB(foo: 7)
let baz: SomeProtocol = bar
bar.foo                     // evaluates to 7
baz.foo                     // evaluates to nil (surprise!)

As someone recently commented on a related Swift bug ticket: "This can be quite surprising and perhaps could be considered a bug of its own?"

It definitely tripped me up.

0
votes

If the protocol provides a default implementation that returns an optional:

protocol SomeProtocol {
    var foo: Int? { get }
}

extension SomeProtocol {
    var foo: Int? { return nil }
}

protocol-conforming types can then provide an overriding non-optional version of the variable/function:

struct StructB: SomeProtocol {
    let foo: Int
}

I found this discussed on the Swift Evolution forum:

At the first glance I thought there is a rule that allows us to satisfy protocol requirements with non-optional types, but this resulted in an error. Only after further investigation I noticed that a default implementation must exist in order to 'kind of override' the requirement with a non-optional version.

https://forums.swift.org/t/how-does-this-rule-work-in-regard-of-ambiguity/19448

This Swift team also discusses allowing non-optional types to satisfy optional-value protocols:

Would it make any sense to allow protocol requirement satisfaction with non-optional types, like with failable init's? (Probably with some implicit optional promotion.)

Yep, totally! Except for the part where this changes the behavior of existing code, so we'd have to be very careful about it. This is considered part of [SR-522] Protocol funcs cannot have covariant returns

which is tracked on Stack Overflow here:

Why can't a get-only property requirement in a protocol be satisfied by a property which conforms?