SE-0404: Allow Protocols to be Nested in Non-Generic Contexts

Hello, Swift community!

The review of SE-0404: Allow Protocols to be Nested in Non-Generic Contexts begins now and runs through August 7th, 2023.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to me as the review manager by email or DM. When contacting the review manager directly, please put "SE-0404" in the subject line.

Trying it out

If you'd like to try this proposal out, you can download a toolchain supporting it for macOS or for Linux. You will need to add -enable-experimental-feature NestedProtocols to your build flags.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  • What is your evaluation of the proposal?
  • Is the problem being addressed significant enough to warrant a change to Swift?
  • Does this proposal fit well with the feel and direction of Swift?
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available at:

https://github.com/apple/swift-evolution/blob/main/process.md

Thank you,

Holly Borla
Review Manager

41 Likes

strong +1

6 Likes

-1
I really hate seeing Swift getting bloat.
I don't think it is necessary.

If it really needs to be added, make it support generic.
I constantly write generic views.

1 Like

This has been bothersome to me for ages and I'm thrilled to see this proposal come to review. Even without the ability to nest in generic contexts I'd find this feature quite useful, and IMO even though the "why" of the restriction on generics get pretty in the weeds, it is at least a relatively bright line for users in terms of when you're able to nest a protocol and when you're not. Absolutely support the non-generic nesting without needing to answer all the thorny questions!

20 Likes

+1 Yes, please!

I'd like for this to be part of the language. It's been too many times when I find myself trying to make a creative name for some protocol which could be just ContextType.ProtocolName

4 Likes

Strong +1, bumped into this limitation many times trying to do exactly what's mentioned on the proposal as the motivation for the feature.

1 Like

+1 I hope one day we can figure out nesting types inside protocols too.

4 Likes

+1 No brainer.

3 Likes

+1 More like a language bug fix than an evolution proposal, honestly.

9 Likes

Very strong +1, it's one of those things that you already expect to work but doesn't.

3 Likes

+1 I would love to be done with writing typealiases just to achieve similar outcomes.

2 Likes

+1

I’ve missed scoping protocols many, many times. A consequence of not having this is that protocol names are longer than necessary and at the same time less clear. A big gap in Swift is filled by this proposal.

Hopefully the standard library can be revised with this in mind for Swift 6.

A quick reading.

Strong +1.

My only reservation is the non-generic scope limit, but I'm assuming supporting generic scopes would complicate the implementation enough to miss the next Swift train out of the station.

I hope in the future we can revisit generic scope support.

+1. I have long been annoyed that this doesn't work, mainly for namespacing reasons.

3 Likes

+1 on this proposal

I think that generic protocols would be really cool and useful, and if they were added it feels like it could be simple to add support for generic contexts.

i wrote up the following intending for it to be a starting point for discussing a new language feature, but as i was finishing it i realized it was really more of an argument for why this proposal should pass.


in C++, i always spent (wasted?) a lot of time thinking about memory safety.

in swift i find that i spend (waste?) a lot of time thinking about lexical hierarchy.

here’s an example of a hypothetical library product that vends one single overwhelmingly important type, and a supporting protocol to be used with that type:

struct Barbie
{
    enum ID {}
    struct Instant {}
    struct Camera<Film> where Film:BarbieFilm 
    {
        struct Photo {}
    }
    ...
}
protocol BarbieFilm
{
    var id:Barbie.ID { get }
    var timestamp:Barbie.Instant { get }
}

i say product and not module because putting both of these definitions in the same module precludes using Barbie as a module name. and in light of that i think it’s helpful to list what i think are some reasonable goals regarding the usability of a library product:

  1. a module’s name should be a description of what the module contains, and not a description of the fact that it is a module. so we really want to use this library as import Barbie and not import BarbieFramework.

  2. library consumers should standardize on one import spelling. in other words, if there is a module named Barbie, but most of the library’s critical functionality is actually @_exported from an umbrella module like BarbieFramework, and import BarbieFramework is the prescribed way to consume the library, we do not want people to type import Barbie and then wonder why nothing is working.

    this is also a way of restating goal #1, if the “public-facing” umbrella module has the simplest name amongst its constituent targets, no one will accidentally import a component of the library when they mean to import the whole thing. (think NIO vs NIOCore.)

  3. library API should obey namespace shadowing rules. in swift, this means if you have a module named Barbie and it also has a top-level type named Barbie, then every other (public) type vended by that module must be lexically scoped to Barbie. things that cannot be nested (like protocols) need to be @_exported from another module with a different name.

  4. library consumers should never utter an internal module name. this is just another way of saying that library consumers should not have to care about a library’s internal dependency graph, and they should not care what module a type is “really defined in”.

    at the pure source code level, this is an equivalent statement to “your library should have a very low probability of name collisions with other code”, because if you never experience a name collision, then you never have to qualify a reference with a module name in the first place.

    and yet “what module a type is actually defined in” is kind of unescapable, it shows up in URLs, it shows up in symbol links, and people get it wrong all the time. so even if you have the most well-designed lexical structure possible, a naming scheme so holy that it will never collide with anyone else’s names, you still have to care about what your inner modules are named, you can’t just prefix them all with underscores.

anyway, i was originally envisioning some kind of “public-facing module name feature”, but then i realized these “usability principles” are really just an elaborate mitigation strategy for the problem described in goal #3: if it weren’t for the fact that we need to reexport protocols from hidden modules with @_exported because they cannot live in the the user-facing module, then we don’t need to lower the rest of the module’s implementation into a hidden sibling module, in fact we can really dispense with the whole “hidden modules” nonsense entirely.

i for one can’t wait for the day where i can stop obsessing about module naming because i will finally be able to declare a protocol in the same module as the code that uses it.

3 Likes

One of the rare times I chime in on the forums but I want this so badly that I felt the need to say +1 +1 +1 would absolutely love to see this.

3 Likes

+1
Whatever argument there is against this feature is moot because you can already bypass this restriction:

protocol _SomeProtocol {}

enum Namespace {
    typealias SomeProtocol = _SomeProtocol
}

This update just lets us declare Namespace.SomeProtocol directly without going through the hoops

11 Likes

+1

Long have we suffered excess identifier noise

1 Like