Skip to content

Instantly share code, notes, and snippets.

@karwa
Last active October 23, 2019 02:40
Show Gist options
  • Save karwa/4c6bff75f8fa84b16df2c8caae97d622 to your computer and use it in GitHub Desktop.
Save karwa/4c6bff75f8fa84b16df2c8caae97d622 to your computer and use it in GitHub Desktop.

Ease restrictions on protocol nesting

During the review process, add the following fields as needed:

Introduction

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

Motivation

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.

Proposed solution

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.

Source compatibility

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.

Effect on ABI stability

Would change the standard library ABI if it chose to adopt the feature.

Effect on API resilience

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.

Alternatives considered

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.

@slavapestov
Copy link

Finally, regarding source stability, what we want to do is ensure that if existing stdlib protocols become nested types, we can compile old code by defining type aliases to map the old names to the new names. Please think this through and see if there are any issues (there shouldn't be).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment