An Enum Based Approach to Namespaces

As @jrose has correctly pointed out the term "submodule" is quite ambiguous, so I will not use that term to refer to my idea, but instead refer to it by the more specific term: "namespace"

Current State of the Art

Currently, in order to create a standalone namespace smaller than a module, one must create an enum with no cases (aka an 'empty' enum). This has a few problems:

  • The namespace looks like a type to coders unfamilar with the use of enum as a namespace, and to further confusion can be extended to conform to protocols.
  • All non-type declarations inside the enum must be marked static which adds boilerplate and reduces readability.
  • There's no way to specify that a member of a nested type should only be visible within its enclosing enum.

Proposed Solution

Introduce a new declaration namespace. This acts as an empty enum except as follows:

  • It may not be used as a type in any context, including generic parameters. Nor may it conform to protocols.
  • A namespace may not be generic and may not be nested inside of a generic type.
  • All non-type declarations are effectively static. Explicitly specifying a top-level namespace variable as static is an error.
  • Declarations with less than public access in nested types or namespaces are only visible within the enclosing namespace or an extension of that namespace, which must still be inside the declaring module or file for internal and (file)private declarations respectively.
  • Protocols may be declared inside of a namespace (we might want to allow that in type declarations in general).
  • A namespace may only be extended from the module it is declared in (although the types inside it may be extended as normal).

Example of Use

public namespace Random {
    // `DefaultRNG`s initializer defaults to `internal`.
    public struct DefaultRNG: RandomNumberGenerator {
        public func next() -> UInt64 { ... }
        ...etc...
    }
    public var defaultRng = DefaultRNG() // initializer is visible here.
}

var otherRng = Random.DefaultRNG() // intializer is not visible here, so compilation fails.

Future Directions

  • Allowing namespaces to be declared inside of generic types. This probably depends on whether and how we decide to handle static stored declarations in generic types. We could always just require that namespaces inside generic types only have computed properties.
  • Allowing one to import a public declaration from a namespace (or all of the public declarations) directly into the current module. Individual declaration import would also be nice to have in general.
  • Sugar for placing the contents of an entire file inside of a namespace. This is purely additive.

Alternatives Considered

  • Do nothing.
  • Allow prefixing enum declarations with static to make all enclosed non-type declarations static, which would solve only the boilerplate issue.
12 Likes

Here's another issue with enums-as-namespaces that should be mentioned: the enum is still a type, and so it generates type metadata in the binary that must be unique or else you end up with duplicate symbols at link time.

This means that if you spread out the definitions in a namespace across multiple files, you may only have a single enum Foo declaration, and the rest are extension Foo. It's asymmetrical and awkward when all you really want is for the declarations to be grouped together and have their names mangled in the same way in the final binary.

It's easy to imagine code generation scenarios where you might want to piece together a number of related declarations across multiple files into the same namespace, and the restriction above forces the generator to have to ensure that there's only a single instance of the actual type declaration instead of just being able to independently produce the namespaced symbols.

3 Likes

related: Namespaces × submodules

That's a good point. That probably makes it worthwhile to just use namespace Foo everywhere that a namespace is used instead of allowing extension Foo.

@taylorsift I feel like your idea and my idea are very close. At the very least the func Namespace.foo() { } syntax is trivial sugar over my proposal, and even if we did base the implementation on enum internally we'd still want something akin to your namespace = Foo syntax. I'm not sure I like the namespace Foo(import Bar) syntax though.

3 Likes

I'm reviving this thread because the enum as namespace thing bugs me every time, and I think this proposal covers the issue really well. There are other threads on this topic too, and AFAICT the arguments against mostly boil down to "let's avoid adding more keywords". But I think the differences between enum and the proposed namespace are valuable.

For example, this nonsense compiles:

enum MyNamespace { ··· }

protocol Silliness
{
  func impossible() -> MyNamespace
}

There may be cases where an uninhabited enum makes sense as a return type (like Never, of course), but not for one that's supposed to be a namespace, and I want to be able to express that.

As proposed, a namespace is mostly a more restricted version of an enum, except for being able to include protocols. If this were to be implemented, what are the potential complications of allowing nested protocols?

14 Likes

..or at least, I thought I was reviving the thread :slight_smile:

I'll try again because I really am curious about what the answers might be. As I said, the counter-arguments I've seen tend to be essentially that it's not worth adding another keyword. IMO this thread has some decent arguments that it is worthwhile, so I'd like to see other angles addressed as well.

Can anyone comment on whether namespaces, especially as proposed here, might be hard to implement? Or why they might otherwise be actively bad?

10 Likes

Reviving this as well lol, right there with you @David_Catmull

I am always using caseless enums as a way to organize my constants, inside of my types... ex:

class MyView: UIView {}

// MARK: Constants

extension MyView {
    enum Layout {
        enum Label {
            static let maxLines = 3
        }

        enum Image {
            static let margin = 5
        }
    }

    enum Style {
        static let background: UIColor = .white

        enum Image {
            static let background: UIColor = .purple
        }
    }
}

I use a lot of nesting, since it makes it really nice on the calling side where each context level is its own namespace, ex calling Layout.Image.margin inside of MyView... and you can use the same identifiers, just in different contexts, ex calling Style.background or Style.Image.background


After browsing different discussions in these forums about submodules and namespaces, I don't think the idea of a submodule really satisfies my use case... from my understanding of the Namespaces × submodules proposal, submodules can be nested within each other, sure... but they can't be nested within types... and I don't want to have to define a MyViewConstants submodule outside of MyView just to be able to define constants, for these reasons:

  • Naming conflicts
  • MyViewConstants should never be more visible than MyView -- which could happen if they were both top level types

What I would like to see is a namespace type that is non-instantiable, non-returnable, non-extendable, can be nested inside of other types, and where you don't need to mark static on everything... purely for the intention of better organization and code readability.

imo that outweighs the argument for not cluttering Swift with more keywords. I've also seen an argument in the forums that caseless enums are an established pattern that people are used to, but in my experience it's usually a pretty opinionated debate about struct with a private initializer vs caseless enum... since it's considered an anti-pattern to what an enum should really be.

1 Like

an alternative:

enum Namespace:Never 
{
    ...
}

that said, i just don’t see much utility in statically guaranteeing that an enum is empty. i don't think i've ever accidentally added a case to a namespace enumeration.

1 Like

In the mean time, I am going to console myself by chanting this:

enum is an excellent namespace umbrella

before each time I employ the services of an uninhabited enum :slight_smile:

But there still remains the problem of protocol names floating in global namespace :frowning_face:

4 Likes