-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Support Private Enum Variants #3506
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
My preference: In a future edition, make enum variants private by default. There is also however the challenge of tuple structs. I think I would probably want to do this:
in all contexts. This means that e.g. |
I feel that privacy goes contrary to the main advantages of enums:
Generally: enums are explicit and direct, structs are implicit and indirect - I think it's nice to have that distinction. Your friend, |
Trying to think of a better alternative. Maybe variants could be allowed to pseudo-alias a type that they also wrap? pub enum Expr {
Array: ExprArray, // Array(ExprArray)
Assign: ExprAssign, // Assign(ExprAssign)
Async: ExprAsync, // Async(ExprAsync)
// ..
} ? Functions and constructors accessed through the variant's path could be syntax sugared: assert_eq!(Expr::Array::from([1, 2]), Expr::Array(ExprArray::from([1, 2]))); ? Matching could be syntax sugared: match text {
Expr::Array { .. } => {},
Expr::Assign { .. } => {},
Expr::Async { .. } => {},
} to match text {
Expr::Array(ExprArray { .. }) => {},
Expr::Assign(ExprAssign { .. }) => {},
Expr::Async(ExprAsync { .. }) => {},
} This is basically what exists already, just a little less repetitive. Maybe it would be too confusing since trying to access a variable after construction would require pattern matching. Alternatively, it could be interesting if each variant counted as its own type and you could implement associated functions and constants per variant (no methods), but I'm sure it's been proposed before. Maybe that would make them seem too type-like when you can't really do anything else with them, like implement traits or use them in function parameters. Your friend, |
@Yokinman what about we look at this from another way, that enums with private variants are equivalent to Just as enums indeed should not add a |
As far as your arguments would concern, the enum pub enum MyThing {
Foo(i32),
Bar(i32),
priv Baz(i32),
priv Qux(i32),
} is functionally equivalent to the current-stable syntax pub enum MyThing {
Foo(i32),
Bar(i32),
Underscore(Underscore),
}
pub struct Underscore(Inner);
enum Inner {
Baz(i32),
Qux(i32),
} except 4 lines shorter and less troublesome to handle, and your argument is basically saying that we should always use a |
I'm not super against enum privacy in terms of fields, but if it existed I think there should be a way to define "write" privacy independently from "read" privacy. So you can match on a variant, but you can't necessarily construct that variant. I think that could definitely be useful. pub enum MaybePrime {
Yes(&priv u64),
No(&priv u64),
}
impl MaybePrime {
pub fn new(num: u64) -> Self {
if num.is_prime() {
Self::Yes(num)
} else {
Self::No(num)
}
}
}
// ..in another crate..
fn num_value(num: MaybePrime) -> u64 {
let (MaybePrime::Yes(x) | MaybePrime::No(x)) = num; // Allowed
x
}
fn make_prime(num: u64) -> MaybePrime {
MaybePrime::Yes(num) // Not allowed
} I find it harder to think of an example where you might want fully private/inaccessible variants, other than specifically Your friend, |
This is not meant as an argument in the discussion, but you can kinda solve this problem today for yourself with the clippy lint // lib.rs
#[non_exhaustive]
pub enum Foo {
Bar,
AddedLater,
}
// main.rs
#[warn(clippy::wildcard_enum_match_arm)]
match foo {
Foo::Bar => todo!(),
_ => todo!(),
} The compiler forced us to add the wildcard before |
I think this is a bad idea, as it forces non-exaustive matching -- which removes a large part of the point of enums. Like Yokinman mentioned, having some way to not allow users to construct certin variants could possibly have merit, but entirely private fields makes no sense to me. |
I disagree. Just because a client of a type shouldn't construct it doesn't make me want to remove the ability of the source of a type to construct it and benefit from sum types in general. It feels a bit baby-with-the-bathwater to not support some valuable features of enums just because one feature is undesired. |
I've paid more attention since I last posted and I've noticed that it's pretty useful to use enums for iterators that switch between multiple modes (surely other kinds of modal things too). You don't want it to be constructable by a user since you might change it later, but you do want to use it in a public return value. Maybe the enum could be marked with an attribute that makes all of its sub-variants private/unconstructable or something? EDIT: Actually, I suppose you can do this already by stuffing a public enum into a private module. It's not intuitive, but it does achieve the function of making the enum inaccessible, yet still usable as an associated type / return type. Or maybe not, since you can still construct it through the associated type. I don't think individually private variants are a good idea, but I like the sound of all variants being private at once. Your friend, |
Thats a neat use case! Like I said above, unconstructable (to the end user) variants makes sense, but making them entirely private defeats the pourpose. AN attribute could defintely be a good way to do it. Though like you mentioned here, if an enum is public access but cannot be constructed manually that makes sense to have all-private variants, even if its basically the same as all-uncontructable variants. You may, for example, want to match against what type of iterator it is. |
That's just marking with the variant with |
@Yokinman for the iterator case, isn't the usual approach to wrap the enum in a struct such that the enum doesn't need to be |
Here's a usecase: I have an enum enum Foo {
Bar,
Baz,
Qux
} And I want to run code when, say, |
One case I frequently run into is mapping non-exhausitive enums from an external system (e.g. a JSON or C API) which represents enums as string or integer. I'd like to preserve unknown values, while acting as similar as possible to a normal non-exhaustive enum in rust. So I'd like to write something like:
From outside the crate this enum would behave like:
Outside the crate the
But traits like Currently it's necessary to resort to ugly workarounds, like putting the enum inside wrapper. |
There should be a way to mark enum variants as private.
Possible syntax:
Motivation
Type authors should be able to use an
enum
to represent their type without exposing that to users. Today if a type is an enum that is visible to users so almost any change is a breaking change (I think only adding an additional private field to a struct-style variant is not breaking).For example if the leading example was written today without
priv
a user would be able to do:Possible Alternatives
#[doc(hidden)]
This hides it from documentation but doesn't actually protect against using it. It is possible that the user copies an old code sample, guesses a name without knowing it is private or an IDE autocompletes it and the user will unknowingly be relying on a (logically) private API.
Internal Enum
This works but results in tedious code. Internals must be accessed as
self.0
and if you want to derive traits this needs to be done on both the wrapper and the impl.Related: #2028 rust-lang/rust#32770
The text was updated successfully, but these errors were encountered: