diff --git a/text/0000-extensible-enums-and-structs.md b/text/0000-extensible-enums-and-structs.md new file mode 100644 index 00000000000..5bbd628633b --- /dev/null +++ b/text/0000-extensible-enums-and-structs.md @@ -0,0 +1,140 @@ +- Start Date: (fill me in with today's date, YYYY-MM-DD) +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary + +Introduce a notation for declaring an enum to be *extensible*, meaning +that more variants or fields may be added in the future. This notation +does not affect the use of the enum within the current crate, but it +prevents downstream crates from employing features (such as exhaustive +matches or struct literals) that would fail to compile if new +variants/fields were added. + +# Motivation + +Imagine that one is writing a public-facing API that includes an +`Error` enum (this exact scenario arises in libstd with the I/O error +enum): + +```rust +pub enum Error { + NoSuchFile, + NoPermissions, +} +``` + +Providing an enum listing out the possible sources of error is a very +convenient API for your users, because they can be use a `match` +statement to easily identify and handle particular kinds of +errors. Unfortunately, it's rather limiting on the future evolution of +your library: if you wish to add a third kind of error in the future, +you will be potentially breaking downstream code. After all, it is +possible that some user was doing an exhaustive match against all +possible sources of error: + +```rust +match error { + Error::NoSuchFile => ..., + Error::NoPermissions => ..., +} +``` + +To resolve this dilemna, this RFC proposes permitting enums to be declared +as *extensible*: + +```rust +pub enum Error { + NoSuchFile, + NoPermissions, + .. +} +``` + +Extensible enums from other crates can never be exhaustively +matched. This means that a downstream user attempting to match against +an error from your library would have to include a wildcard option: + +```rust +// from an external crate +match error { + Error::NoSuchFile => ..., + Error::NoPermissions => ..., + _ => ..., +} +``` + +Due to the presence of this wildcard, you can safely add variants +without fear of breaking source compatibility with downstream clients. + +Note that extensibility is ignored within the current +crate. Therefore, your library may internally write a match that +exhaustively covers all sources of error. You don't need to worry +about source compatibility with yourself, after all. + +# Detailed design + +Enum grammar is extended to permit a list of variants to be terminated +with `..`. An enum declared with `..` is considered *extensible*. + +Extensible enums that are local to the current crate are treated the +same as any other enum. Exhaustiveness checking is modified so that +extensible enums that are not local to the current crate are never +considered completely covered. (As if there was one additional variant +beyond those declared, essentially.) + +Extensibility does not currently affect the enum in any other way. The +representation in particular remains unchanged. This RFC does not +attempt to address binary-level compatibility, only source +compatibility. + +# Drawbacks + +More language syntax. + +# Alternatives + +**Private enum variants.** There are various privacy-based workarounds +one could use to get a similar guarantee. For example, if private +variants were permitted (or perhaps variants were private by default), +then one could add a private variant to the enum. Privacy however is not a perfect fit: + +1. Private variants would also be private within your crate, + preventing you from using local exhaustive matching as well. +2. Even if you work around the previous problem and manage to make + enum variants visible only within the current crate, all of your + match statements will include an extra, unreachable arm + corresponding to this private variant (since it never occurs in + practice). + +**Extensible structs.** In the same way that enums can be extended +with more variants, it might be useful to be able to declare structs +as being extensible with additional fields. This would prevent the +various bits of syntax (struct literal matching, struct literal +constructors) that require knowledge of the full set of fields to +work. Originally this feature was intended for inclusion in this RFC +but was omitted to keep things simple. The need to add more fields in +the future is both rather unusual and easily addressed using a dummy +private field (private fields do have some of the same downsides as +private enum variants, though). In practice though most libraries will +just declare all fields as private and use getters to access their +contents, which is a better pattern in any case. + +**Treat extensible enums as extensible also within the local crate.** +The current design states that extensible enums are only considered +"extensible" outside the current crate. The intention was to allow for +more precision locally, so that users can still take advantage of +exhaustive matches. However, this introduces an asymmetry between +crates; further, in many real-world use cases (e.g., `IoError`), it is +unlikely that even the local crate will ever want to exhaustively +match all variants, so this extra expressive power may not be +necessary. + +# Unresolved questions + +Is there a way to have downstream crates get warnings -- if not errors +-- when a match is not exhaustive? One suggestion was to add an +alternate wildcard syntax that indicates a match that is intended to +be exhaustive, even if a backup must be provided. But it is unclear +what that wildcard syntax should be (the suggestion was `..`, but that +is ambiguous with existing syntax).