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
enum
s with nocase
s 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: MyStaticProtocol
is 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-
case
enum
will either have to be@final
Or 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.