- Proposal: SE-NNNN
- Authors: Karl Wagner
- Review Manager: TBD
- Status: Awaiting review
During the review process, add the following fields as needed:
- Decision Notes: Rationale, Additional Commentary
- Bugs: SR-NNNN, SR-MMMM
- Previous Revision: 1
- Previous Proposal: SE-XXXX
Allow protocols to be nested in other types, and for other types (including other protocols) to be nested inside protocols, subject to a few constraints.
Swift-evolution thread: Discussion thread topic for that proposal
Nesting types inside other types allows us to scope their usage and provide a cleaner interface. Protocols are an important part of Swift, and many popular patterns (for example, the delegate pattern) define protocols which are intended to be used in the context of other types. It would be nice to apply type-nesting here: MyClass.Delegate
reads better than MyClassDelegate
, and literally brings structure to large frameworks.
Similarly, we have examples in the standard library where supporting types are defined with the intention that they be used in the context of some protocol - FloatingPointClassification
, FloatingPointSign
, and FloatingPointRoundingRule
are enums which are used by various members of the FloatingPoint
protocol. It would also be nice to apply type-nesting here, with the enums belonging to the protocol itself - e.g. FloatingPoint.Sign
.
There are two important restrictions to this proposal:
- Nested protocols may not capture generic type parameters from their contexts
- Nested types (including protocols) may not capture associated types from their contexts
These restrictions are due to currently-limited support for existential types. There are many interesting ideas to overcome both, but after discussions on the mailing lists, we should be able to handle most of the common cases with these limitations and it keeps things reasonable to implement.
The first part is to allow protocols to be nested inside of structural types (for example, in the delegate pattern):
class AView : MYView {
protocol Delegate : class {
func somethingHappened()
}
weak var delegate : Delegate?
func doSomething() {
//...
delegate?.somethingHappened()
}
}
class AController : MYViewController, AView.Delegate {
func somethingHappened() {
// Respond to callback
}
}
Similarly, we will allow structural types to be nested inside of protocols (such as the standard library's FloatingPoint*
enums):
protocol FloatingPoint {
enum Sign {
case plus
case minus
}
var sign: Sign { get }
}
struct Float : FloatingPoint {
var sign: FloatingPoint.Sign { /* return the sign */ }
}
And the same for protocols inside of protocols:
protocol TextStream {
protocol Transformer {
func transform(_: Character) -> Character
}
var transformers : [Transformer] { get set }
func getNextCharacter() -> Character
}
struct WeLoveUmlauts : TextStream.Transformer {
func transform(_ char: Character) -> Character {
switch char {
case "a".characters.first!: return "ä"
case "e".characters.first!: return "ë"
//...etc
default: return char
}
}
}
In all of the examples, any of the structual types may have generic types, and any of the protocols may have associated types. So long as the restrictions mentioned earlier are observed, i.e. that no types are captured between a protocol and its outer or inner types.
This change is additive, although there are a couple of places in the standard library where we might consider reorganising things after this change. Those changes are not a part of this proposal.
Would change the standard library ABI if it chose to adopt the feature.
Nesting changes the name (both in source and symbolic) of the relevant types. Has the same effect as other type renamings/nesting and un-nesting.
The alternative is to namespace your types manually with a prefix, similar to what the standard library, Apple SDK overlays, and existing Swift programs already do. However, nested types and cleaner namespaces are one of the little things that developers - espcially coming from Objective-C - have always been really excited about. From time to time somebody pops up on the mailing list to ask why we don't have it yet for protocols, and changes proposed here usually are met with broad support.
I think the terminology and distinctions used here are not quite correct. I suggest replacing "simple protocols" and mention of generic types with the following:
As far as ABI resilience goes, the key concern is whether adding members to nested types in protocols changes the ABI. Normally, adding a new protocol requirement does not change ABI as long as the requirement has a default implementation provided by an unconstrained protocol extension. However, if capturing an outer associated type is implemented by adding a hidden
Self
generic parameter to the nested type, then it will be possible to change ABI, by adding a new requirement that referencesSelf
in the case where no existing requirement referencesSelf
.