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.
6 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?

12 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?

6 Likes
Terms of Service

Privacy Policy

Cookie Policy