Alternative to pub struct Foo(FooInner)?

Back in the day it was necessary to do

enum FooInner {
A,
B,
}
pub struct Foo(FooInner);

Is this ergonomic thorn still necessary?

1 Like

If you wish to make the variants private, yes.

ugh is this thorn ever gonna get fixed

what's even the point of this thorn anyway. what benefit does it have.

If you feel this passionately about it, you can always write an RFC for some mechanism to make all enum variants private.

As a historical note, this feature was removed by RFC 26; before that, one could use the priv keyword to create private variants in public enums. Most of the discussion around the change occurred in the issue rust-lang/rust#8122.

11 Likes

in an ideal world you'd be able to treat enums as "just another form of type". as they should be.

why do structs get encapsulation at all if enums don't? why do you have to newtype your enums? this pattern exists a lot in std and it's awkward having to wrap and unwrap everywhere simply because the compiler doesn't provide these with first-class support for encapsulation.

also we don't even want "individual variants" we want straight-up all-or-nothing.

I have indeed felt this, and wonder if some visibility modifier like

pub(notimplied) enum Foo { PrivVariant }

Which flips the pub default on variants to priv, keeping the ^^^ pub not permitted here because it's implied type of errors. So still disallowing mixed visibility enums.

I don't think pub(notimplied) is particularly good name, but :person_shrugging:

It looks like the discussion on that RFC was generally in favor of [1] a way to make all enum variants private, so an RFC for a way to do that (as opposed to marking individual variants private) might be accepted.

I agree that wrapping with a struct for this feels a little goofy.

Maybe something like pub opaque enum Foo for enums that should be treated like a struct with private fields?


  1. or at least not AGAINST ↩ī¸Ž

2 Likes

It would have been nice if the default for variants had been private, same as for fields in structs. Then presumably this would be possible:

enum Foo {
    PrivateVariant
    pub PublicVariant { priv_field: String, pub pub_field: u8 } 
} 

Presumably it could even be done with an edition, using something like rust fix or other tooling eg RA to autoconvert the code.

However, there could be drawbacks that at present I'm not aware of.

as a workaround, we could have macros like encapsulated_enum! and encapsulated_match! in a library crate, which just abstract away all of this stuff, maybe?

You could probably create a custom #[private_enum] attribute which generates the wrapper struct plus a #[doc(hidden)] implementation for Deref so pattern matching on references still works inside your crate.

I was just yesterday trying to talk about having a type alias that doesn't leak the aliased type but doesn't require you to have .0 everywhere in your crate like a newtype. At least part of the problem is that "opaque type alias" as a phrase got stolen by "type alias impl trait" (TAIT).

So yeah, bring back priv :smile:

wouldn't Deref leak?

that's what Self(self) would be for ("destructurable self" maybe?), but you'd still end up putting Self(foo) everywhere.

Kinda? I don't think it would be an issue in practice.

If the Deref impl is #[doc(hidden)] then it won't be visible in API docs. Similarly, because FooInner isn't exported they have no way of naming the type (or its variants - so match won't work). The only way details can be leaked are if the user calls public methods on the enum (of which there should be none) or trait methods (which they can only find out by reading the source code, anyway).

<Foo as Deref>::Target::Variant because you'd need the enum to be pub for that.

Yeah, the enum would have to be public for Deref to work.

we feel like

opaque_enum! {
  pub(in path) Foo {
    Variant, ...
  }
}

impl Foo {
  fn foo(self) {
    opaque_match! {
      self {
        Foo::Variant => { ... },
        ...
      }
    }
  }
}

would be the best workaround for this use-case.