Pitch: non-static & non-generic attributes for protocols

Here I will propose a couple of minor features for protocols, designed to provide a slight loosening of certain compiler restrictions, to provide more options for authors in situations where they can be satisfied with limiting the future evolvability of the code in which these features might be used.

The Problem This Proposal Aims to Address

Presently, the following code does not compile:

protocol Fooey {
    var bar: Int { get }
}

struct Foo: Fooey {
    var bar: Int { 42 }
}

enum Qux {
    static func check<T: Fooey>(_ foo: T) {
        // do something
    }
}

let foo: Fooey = Foo()

Qux.check(foo)

That last line gives the compiler error:

Value of protocol type 'Fooey' cannot conform to 'Fooey'; only struct/enum/class types can conform to protocols

We're all familiar with this scenario, but just for a quick recap, Swift's official documentation on the matter of protocol non-conformance (link) mentions one possible workaround: if we prepend the first line with @objc (and precede that with import Foundation), then we can avoid this compiler error—as long as the protocol has no static (or initializer) requirements. (Thanks to @xwu for the details on this.)

Of course, there are some major downsides to using an @objc protocol:

  • they only work with classes. (struct Foo must become class Foo)
  • they cannot require functions that have generic parameters

The documentation linked above also explains why we cannot currently allow a similar exception to the rule for a non-@objc protocol type, which also has no static or initializer requirements:

Currently, even if a protocol P requires no initializers or static members, the existential type P does not conform to P (with exceptions below). This restriction allows library authors to add such requirements (initializers or static members) to an existing protocol without breaking their users' source code.

Proposed solution

Add the following new attributes, applicable to any non-@objc protocol declaration:

  1. non-static — promises that the protocol will never have a static or initializer requirement added.
  2. non-generic — promises that the protocol will never have a self or associated-type requirement added. (I.e., it will never become a PAT)

This would allow an author, who specifically wants some protocol Fooey to support being used as a protocol type that conforms to itself, to declare Fooey in the following way to achieve that goal: non-static non-generic protocol Fooey. Now, the rest of the original example would compile, because the compiler can now trust that we do not need to worry about the completely valid library evolution concerns mentioned above.

I could also imagine that these attributes might allow for some additional compiler optimization for libraries that are being built for evolution, but where the author is 100% sure they will never add static and/or PAT-type requirements to the protocol.

Note: if anyone has already suggested this within the last 2 years, I couldn't find it. If it was suggested before then, and I somehow missed it, please reply with a link, and accept my apologies in advance.

PS—A spiritual cousin to the proposed attributes might be the recently added frozen. The rationale is similar: just like frozen, these new attributes are purely opt-in, and serve only to communicate to the compiler some explicit refinements/limits of the developer's intentions in a maintainable way that future developers will not be likely to accidentally violate.

I look forward to hearing what the "gotchas" are that I have not considered. Thanks

4 Likes

Self-conformance of protocols has been suggested actually many times. For example, @Jumhyn explicitly referenced your other thread in launching a discussion:

4 Likes

@xwu Thank you, I will read up on the other thread.