swiftprotocolsoption-type

Swift Protocol Optional conformance via Non-Optional


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?


Solution

  • 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?