Access control for enum case initializers

That is hard to wrap the head around, can you define a pattern? Does not a pattern in a usual case require an equality test? (I know there are other patterns, but in the above example I could imagine there is an internal ~= that falls back to ==.)

Since you limited case a with private(init), the static case initializer would not leak out to other files, which in my brain will prevent you from writing case .a: in the switch statement in another file.

This was a perfect example why I think pattern matching will error out in your example as well.

If that's not true, then I guess I don't understand pattern matching after 5 years working in Swift.

2 Likes

Not in general, no. The way enums are matched is different from the way matches using ~=works.

No. The entire point of the parameterized access control is to not prevent this. That would make values of the enum less ergonomic to work with for users of a library. But just because a library wants to expose pattern matches on its values does not necessarily mean it wants those values to be initializable by users of the library. Imagine if a public struct could only have public initializers. That would be nuts. But that's exactly the situation we have right now for enums.

If you're talking about an example that uses ~= to match a value of a different type then yes, that will produce a compiler error under my pitch. That is because the case initializer is required to construct a value. The case pattern is not being used at all in this example. It just looks like it is because of how ~= patterns and case initializers work.

:exploding_head:

Okay then I need to apologize for derailing this thread so much with false information. Sorry @cukr if I confused you and other readers. I guess I need to do more homework and dig up more in depth documentation about pattern matching in Swift.

I at first would think that as well, but it's easy to find a counter-example:

enum Foo {
  case foo(Int)
}
func checkFoo(_ foo: Foo) {
  switch foo {
  case .foo: print("foo")
  }
}

Is the case initialized here to do the pattern match? :slight_smile:

1 Like

Oof. This thread got super derailed. For what it's worth, I also think it's worth supporting cases that have less access than the enum for pattern-matching as well, but that's a separate topic that deserves its own thread. Matthew's idea of "you can't construct these but you can consume them" is reasonable for the same reason we have structs with let properties: because a case may have invariants that go beyond just the types.

9 Likes

Do I understand correctly that this proposal would allow something like this?

enum Parity<T: BinaryInteger> {
  private(init) case even(T)
  private(init) case odd(T)

  init(_ n: T) {
    self = n.isEven ? .even(n) : .odd(n)
  }
}

(Or perhaps more usefully, an enum with an ascii(UInt8) case that enforces its value is less than 128.)

7 Likes

Yes that is a good example of why we need this capability.

3 Likes

Oh I see, so you’re not talking about hiding/scoping any cases, but just the initializers. So I can still test against it in switch, if, or guard, but just can’t create one.

So this doesn’t mess up allcases or anything else really.

If I have this right, then I don’t see any downsides and it’s something I would use.

Please help to make this a reality, I deadly need enum cases with private init, sometimes it just feel unnatural to model something as a struct or class.

1 Like