Hello, Swift community!
I’d like to solicit some feedback about an idea that I’ve got.
Introduction
A notable part of SE-0302: Sendable and @Sendable closures is the formal concept of a marker protocol (declared with the new compiler-internal @_marker attribute) that can be applied to a protocol declaration, which imposes the following rules on that protocol:
- It can’t have requirements of any kind.
- It can’t refine a non-marker protocol.
- It can’t be used as the second operand of
is,as?andas!operators. - It can’t be used inside a generic type constraint expression, which is defining a conditional conformance to a non-marker protocol.
This is a very useful tool for defining completely compile-time protocols that are predominantly used by the compiler for enforcing special rules.
Inspired by this concept and recalling a very notable use case (see below), I’d like to pitch the idea of adding the concept of a static protocol.
Motivation
One very powerful design pattern that is used extensively within SwiftUI is what I’m calling “the static protocol pattern”, where a protocol is only used for its static members and the conforming types are never expected to have an instance and instead are used as a metatype instance.
Most notable examples are EnvironmentKey and PreferenceKey.
First-class support for static protocols would help streamline the usage of this design pattern and make it easier (especially for newcomers) to avoid confusion and misuse.
Detailed Design
A static protocol is defined like a regular protocol, but with the static keyword applied to it:
static protocol EnvironmentKey {
associatedtype Value
static var defaultValue: Value { get }
}
A static protocol is governed by these rules:
- It can’t have requirements that explicitly or implicitly mention an instance of
Self(e.g. non-static members, initializers, operators, etc…). - It can’t refine a non-static protocol.
- It can’t be refined by a non-static protocol.
- It can’t be used as the second operand of
is,as?andas!operators except in the form of a metatype (e.g.something is MyStaticProtocol.Type.self). - It can’t be conformed to by an inhabitable type (for now, only
enums with nocases can conform to a static protocol). - It can’t directly or indirectly become a type constraint for a non-metatype instance. For example, if it’s a constraint on type
T, then there cannot exist an instance of typeT(associatedtype T: Collection where Element: MyStaticProtocolis an error). - Conformance to a static protocol cannot be added to a type that may become inhabitable in an ABI-compatible manner. For example, a no-
caseenumwill either have to be@finalOr the conformance will have to be specified in the same module as theenum.
Future Directions
One notable future direction would be to add syntactic sugar to make working with static protocol metatypes easier, seeing as there can never be an instance of a static protocol.