Skip to content

Commit c733e04

Browse files
committed
Alter RFC to a pre-1.0 implementation strategy
1 parent e9fcfa7 commit c733e04

File tree

1 file changed

+50
-161
lines changed

1 file changed

+50
-161
lines changed

active/0000-enum-namespacing.md

+50-161
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@
55
# Summary
66

77
The variants of an enum are currently defined in the same namespace as the enum
8-
itself. This RFC proposes to define variants under the enum's namespace. Since
9-
1.0 is fast approaching, this RFC outlines a path which requires a single,
10-
small, change before 1.0 that allows for the remainder to be implemented after
11-
1.0 while preserving backwards compatibility.
8+
itself. This RFC proposes to define variants under the enum's namespace.
129

1310
## Note
1411

@@ -230,147 +227,30 @@ use messages::{FrontendMessage, BackendMessage, WriteMessage, ReadMessage};
230227

231228
# Detailed design
232229

233-
The implementation is split into several stages. Only the first needs to be
234-
implemented before 1.0.
235-
236-
## Before 1.0
237-
238-
To ensure that the future changes preserve backwards compatibility, we must
239-
add a small restriction: an inherent `impl` for an enum may not define any
240-
methods with the same name as any of the enum's variants. For example, both
241-
methods defined for `Foo` would be forbidden:
242-
```rust
243-
enum Foo {
244-
Bar,
245-
Baz,
246-
}
247-
248-
impl Foo {
249-
fn Bar(&self) {}
250-
fn Baz() {}
251-
}
252-
```
253-
This should not affect much if any code in practice, as Rust's naming
254-
conventions state that variants should be capitalized and method names should
255-
not.
256-
257-
## With other post-1.0 work
258-
259-
Part of the UFCS work will (or at least discussed) allowing this:
260-
```
261-
trait Foo {
262-
fn Baz();
263-
}
264-
265-
enum Bar {
266-
Baz,
267-
}
268-
269-
impl Foo for Bar {
270-
fn Baz() {}
271-
}
272-
273-
fn main() {
274-
Bar::Baz(); // instead of Foo::<for Bar>::Baz()
275-
}
276-
```
277-
If this is implemented before namespaced enums, we must add a restriction,
278-
similar to the one for intrinsic methods, that prohibits something like
279-
`Foo::Baz` from being called as in the above example. Note that the trait may
280-
be implemented for `Bar` without any problems, and the method may be called via
281-
the long form. It's just `Bar::Baz()` that must be disallowed.
282-
283-
## Later
284-
285-
The compiler's resolve stage will be altered in two ways. It will place an
286-
enum's variants into the enum's sub-namespace in a similar way to methods
287-
defined in inherent `impl`s. Note that there is one key difference between
288-
those methods and the variants: the methods cannot be `use`d, while the
289-
variants can be.
290-
291-
In addition, when searching for a name in some namespace and the name is not
292-
present, the compiler will search all enums defined in that namespace. If
293-
exactly one variant with the proper name is found, the name will resolve to
294-
that variant. If there are multiple variants with the same name, an error will
295-
be raised (It isn't possible to end up in this situation in code written before
296-
this is implemented, but will be possible in code written after). This ensures
297-
that the namespacing change is fully backwards compatible. This behavior is
298-
more conservative than it needs to be - it could try to use type inference to
299-
determine which of the multiple variants should be selected, for example.
300-
However, the fallback is designed to be just that - a fallback to make sure
301-
that code does not break in the change. It is not intended to be depended on by
302-
code written after the change has been made.
303-
304-
### Examples
305-
306-
```rust
307-
enum Foo {
308-
Bar,
309-
Baz,
310-
Buz,
311-
}
312-
313-
fn ok(f: Foo) {
314-
use Foo::Buz;
315-
316-
match f {
317-
Bar => {} // backwards compatibility fallback
318-
Foo::Baz => {}
319-
Buz => {}
320-
}
321-
}
322-
```
323-
324-
```rust
325-
enum Fruit {
326-
Apple,
327-
Orange,
328-
}
329-
330-
enum Color {
331-
Red,
332-
Orange,
333-
}
334-
335-
fn broken(f: Fruit) {
336-
match f {
337-
Apple => {} // fallback
338-
Orange => {} // ~ ERROR ambiguous reference to a variant
339-
}
340-
}
341-
342-
fn ok(f: Fruit) {
343-
use Fruit::{Apple, Orange};
344-
match f {
345-
Apple => {}
346-
Orange => {}
347-
}
348-
}
349-
```
350-
351-
```rust
352-
enum Foo {
353-
Bar,
354-
Baz
355-
}
356-
357-
struct Bar;
358-
359-
fn broken(f: Foo) {
360-
match f {
361-
Bar => {} // ~ ERROR expected a `Foo` but found type `Bar`
362-
Baz => {}
363-
}
364-
}
365-
```
366-
367-
In addition, we can add a default-allow lint that identifies use of the
368-
compatibility fallback. This will allow developers to minimize the chance of
369-
breakage down the line if we decide to remove this fallback in Rust 2.0 as well
370-
as avoid code that will presumably end up being considered bad style. Note that
371-
this would not trigger if the variants are explicitly reexported in the module,
372-
so enums that are designed to be used in the current manner will not be
373-
affected.
230+
The compiler's resolve stage will be altered to place the value and type
231+
definitions for variants in their enum's module, just as methods of inherent
232+
impls are. Variants will be handled differently than those methods are,
233+
however. Methods cannot currently be directly imported via `use`, while
234+
variants will be. The determination of importability currently happens at the
235+
module level. This logic will be adjusted to move that determination to the
236+
definition level. Specifically, each definition will track its "importability",
237+
just as it currently tracks its "publicness". All definitions will be
238+
importable except for methods in implementations and trait declarations.
239+
240+
The implementation will happen in two stages. In the first stage, resolve will
241+
be altered as described above. However, variants will be defined in *both* the
242+
flat namespace and nested namespace. This is necessary t keep the compiler
243+
bootstrapping.
244+
245+
After a new stage 0 snapshot, the standard library will be ported and resolve
246+
will be updated to remove variant definitions in the flat namespace. This will
247+
happen as one atomic PR to keep the implementation phase as compressed as
248+
possible. In addition, if unforseen problems arise during this set of work, we
249+
can roll back the initial commit and put the change off until after 1.0, with
250+
only a small pre-1.0 change required. This initial conversion will focus on
251+
making the minimal set of changes required to port the compiler and standard
252+
libraries by reexporting variants in the old location. Later work can alter
253+
the APIs to take advantage of the new definition locations.
374254

375255
## Library changes
376256

@@ -392,28 +272,37 @@ pub enum Item {
392272
Static(Bar),
393273
}
394274
```
395-
In the Rust standard library, this can be used to update libraries to a cleaner
396-
API without breaking backwards compatibility if the library changes are made in
397-
the same release as the language change.
275+
To simply keep existing code compiling, a glob reexport will suffice:
276+
```rust
277+
pub use Item::*;
398278

399-
# Drawbacks
279+
pub enum Item {
280+
ItemStruct(Foo),
281+
ItemStatic(Bar),
282+
}
283+
```
284+
Once RFC #385 is implemented, it will be possible to write a syntax extension
285+
that will automatically generate the reexport:
286+
```rust
287+
#[flatten]
288+
pub enum Item {
289+
ItemStruct(Foo),
290+
ItemStatic(Bar),
291+
}
292+
```
400293

401-
This will cause a period of uncertainty in libraries, where libraries that
402-
aren't update become unidiomatic and libraries that are updated accumulate
403-
cruft if they attempt to maintain backwards compatibility.
294+
# Drawbacks
404295

405-
The compatibility fallback logic will add complexity to resolve, and with
406-
increased complexity comes an increased chance of bugs.
296+
The transition period will cause enormous breakage in downstream code. It is
297+
also a fairly large change to make to resolve, which is already a bit fragile.
407298

408299
# Alternatives
409300

410-
We can push to switch entirely to namespaced enums before 1.0. This will allow
411-
us to avoid the compatibility fallback and minimize library cruft, at the cost
412-
of adding a significant amount of work to the 1.0 docket. If we decide to go
413-
down this route, we could use the same implementation strategy laid out here to
414-
avoid breaking the entire world at once - implement the fallback and lint at a
415-
default (or forced) `warn` level and then remove both after some period of
416-
time.
301+
We can implement enum namespacing after 1.0 by adding a "fallback" case to
302+
resolve, where variants can be referenced from their "flat" definition location
303+
if no other definition would conflict in that namespace. In the grand scheme of
304+
hacks to preserve backwards compatibility, this is not that bad, but still
305+
decidedly worse than not having to worry about fallback at all.
417306

418307
Earlier iterations of namespaced enum proposals suggested preserving flat enums
419308
and adding `enum mod` syntax for namespaced enums. However, variant namespacing

0 commit comments

Comments
 (0)