|
| 1 | +- Start Date: 2014-06-12 |
| 2 | +- RFC PR #: |
| 3 | +- Rust Issue #: |
| 4 | + |
| 5 | +# Summary |
| 6 | + |
| 7 | +Remove or feature gate the shadowing of view items on the same scope level, in order to have less |
| 8 | +complicated semantic and be more future proof for module system changes or experiments. |
| 9 | + |
| 10 | +This means the names brought in scope by `extern crate` and `use` may never collide with |
| 11 | +each other, nor with any other item (unless they live in different namespaces). |
| 12 | +Eg, this will no longer work: |
| 13 | + |
| 14 | +```rust |
| 15 | +extern crate foo; |
| 16 | +use foo::bar::foo; // ERROR: There is already a module `foo` in scope |
| 17 | +``` |
| 18 | + |
| 19 | +Shadowing would still be allowed in case of lexical scoping, so this continues to work: |
| 20 | + |
| 21 | +```rust |
| 22 | +extern crate foo; |
| 23 | + |
| 24 | +fn bar() { |
| 25 | + use foo::bar::foo; // Shadows the outer foo |
| 26 | + |
| 27 | + foo::baz(); |
| 28 | +} |
| 29 | + |
| 30 | +``` |
| 31 | +# Definitions |
| 32 | +Due to a certain lack of official, clearly defined semantics and terminology, a list of relevant |
| 33 | +definitions is included: |
| 34 | + |
| 35 | +- __Scope__ |
| 36 | + A _scope_ in Rust is basically defined by a block, following the rules of lexical |
| 37 | + scoping: |
| 38 | + |
| 39 | + ``` |
| 40 | + scope 1 (visible: scope 1) |
| 41 | + { |
| 42 | + scope 1-1 (visible: scope 1, scope 1-1) |
| 43 | + { |
| 44 | + scope 1-1-1 (visible: scope 1, scope 1-1, scope 1-1-1) |
| 45 | + } |
| 46 | + scope 1-1 |
| 47 | + { |
| 48 | + scope 1-1-2 |
| 49 | + } |
| 50 | + scope 1-1 |
| 51 | + } |
| 52 | + scope 1 |
| 53 | + ``` |
| 54 | + |
| 55 | + Blocks include block expressions, `fn` items and `mod` items, but not things like |
| 56 | + `extern`, `enum` or `struct`. Additionally, `mod` is special in that it isolates itself from |
| 57 | + parent scopes. |
| 58 | +- __Scope Level__ |
| 59 | + Anything with the same name in the example above is on the same scope level. |
| 60 | + In a scope level, all names defined in parent scopes are visible, but can be shadowed |
| 61 | + by a new definition with the same name, which will be in scope for that scope itself and all its |
| 62 | + child scopes. |
| 63 | +- __Namespace__ |
| 64 | + Rust has different namespaces, and the scoping rules apply to each one separately. |
| 65 | + The exact number of different namespaces is not well defined, but they are roughly |
| 66 | + - types (`enum Foo {}`) |
| 67 | + - modules (`mod foo {}`) |
| 68 | + - item values (`static FOO: uint = 0;`) |
| 69 | + - local values (`let foo = 0;`) |
| 70 | + - lifetimes (`impl<'a> ...`) |
| 71 | + - macros (`macro_rules! foo {...}`) |
| 72 | +- __Definition Item__ |
| 73 | + Declarations that create new entities in a crate are called (by the author) |
| 74 | + definition items. They include `struct`, `enum`, `mod`, `fn`, etc. |
| 75 | + Each of them creates a name in the type, module, item value or macro namespace in the same |
| 76 | + scope level they are written in. |
| 77 | +- __View Item__ |
| 78 | + Declarations that just create aliases to existing declarations in a crate are called |
| 79 | + view items. They include `use` and `extern crate`, and also create a name in the type, |
| 80 | + module, item value or macro namespace in the same scope level they are written in. |
| 81 | +- __Item__ |
| 82 | + Both definition items and view items together are collectively called items. |
| 83 | +- __Shadowing__ |
| 84 | + While the principle of shadowing exists in all namespaces, there are different forms of it: |
| 85 | + - item-style: Declarations shadow names from outer scopes, and are visible everywhere in their |
| 86 | + own, including lexically before their own definition. |
| 87 | + This requires there to be only one definition with the same name and namespace per scope level. |
| 88 | + Types, modules, item values and lifetimes fall under these rules. |
| 89 | + - sequentially: Declarations shadow names that are lexically before them, both in parent scopes |
| 90 | + and their own. This means you can reuse the same name in the same scope, but a definition |
| 91 | + will not be visibly before itself. This is how local values and macros work. |
| 92 | + (Due to sequential code execution and parsing, respectively) |
| 93 | + - _view item_: |
| 94 | + A special case exists with view items; In the same scope level, |
| 95 | + `extern crate` creates entries in the module namespace, which are shadowable by names created |
| 96 | + with `use`, which are shadowable with any definition item. |
| 97 | + __The singular goal of this RFC is to remove this shadowing behavior of view items__ |
| 98 | + |
| 99 | +# Motivation |
| 100 | + |
| 101 | +As explained above, what is currently visible under which namespace in a given scope is determined |
| 102 | +by a somewhat complicated three step process: |
| 103 | + |
| 104 | +1. First, every `extern crate` item creates a name in the module namespace. |
| 105 | +2. Then, every `use` can create a name in any namespace, |
| 106 | + shadowing the `extern crate` ones. |
| 107 | +3. Lastly, any definition item can shadow any name brought in scope by both `extern crate` and `use`. |
| 108 | + |
| 109 | +These rules have developed mostly in response to the older, more complicated import system, and |
| 110 | +the existence of wildcard imports (`use foo::*`). |
| 111 | +In the case of wildcard imports, this shadowing behavior prevents local code from breaking if the |
| 112 | +source module gets updated to include new names that happen to be defined locally. |
| 113 | + |
| 114 | +However, wildcard imports are now feature gated, and name conflicts in general can be resolved by |
| 115 | +using the renaming feature of `extern crate` and `use`, so in the current non-gated state |
| 116 | +of the language there is no need for this shadowing behavior. |
| 117 | + |
| 118 | +Gating it off opens the door to remove it altogether in a backwards compatible way, or to |
| 119 | +re-enable it in case wildcard imports are officially supported again. |
| 120 | + |
| 121 | +It also makes the mental model around items simpler: Any shadowing of items happens through |
| 122 | +lexical scoping only, and every item can be considered unordered and mutually recursive. |
| 123 | + |
| 124 | +If this RFC gets accepted, a possible next step would be a RFC to lift the ordering restriction |
| 125 | +between `extern crate`, `use` and definition items, which would make them truly behave the same in |
| 126 | +regard to shadowing and the ability to be reordered. It would also lift the weirdness of |
| 127 | +`use foo::bar; mod foo;`. |
| 128 | + |
| 129 | +Implementing this RFC would also not change anything about how name resolution works, as its just |
| 130 | +a tightening of the existing rules. |
| 131 | + |
| 132 | +# Drawbacks |
| 133 | + |
| 134 | +- Feature gating import shadowing might break some code using `#[feature(globs)]`. |
| 135 | +- The behavior of `libstd`s prelude becomes more magical if it still allows shadowing, |
| 136 | + but this could be de-magified again by a new feature, see below in unresolved questions. |
| 137 | +- Or the utility of `libstd`s prelude becomes more restricted if it doesn't allow shadowing. |
| 138 | + |
| 139 | +# Detailed design |
| 140 | + |
| 141 | +A new feature gate `import_shadowing` gets created. |
| 142 | + |
| 143 | +During the name resolution phase of compilation, every time the compiler detects a shadowing |
| 144 | +between `extern crate`, `use` and definition items in the same scope level, |
| 145 | +it bails out unless the feature gate got enabled. This amounts to two rules: |
| 146 | + |
| 147 | +- Items in the same scope level and either the type, module, item value or lifetime namespace |
| 148 | + may not shadow each other in the respective namespace. |
| 149 | +- Items may shadow names from outer scopes in any namespace. |
| 150 | + |
| 151 | +Just like for the `globs` feature, the `libstd` prelude import would be preempt from this, |
| 152 | +and still be allowed to be shadowed. |
| 153 | + |
| 154 | +# Alternatives |
| 155 | + |
| 156 | +The alternative is to do nothing, and risk running into a backwards compatibility hazard, |
| 157 | +or committing to make a final design decision around the whole module system before 1.0 gets |
| 158 | +released. |
| 159 | + |
| 160 | +# Unresolved questions |
| 161 | + |
| 162 | +- It is unclear how the `libstd` preludes fits into this. |
| 163 | + |
| 164 | + On the one hand, it basically acts like a hidden `use std::prelude::*;` import |
| 165 | + which ignores the `globs` feature, so it could simply also ignore the |
| 166 | + `import_shadowing` feature as well, and the rule becomes that the prelude is a magic |
| 167 | + compiler feature that injects imports into every module but doesn't prevent the user |
| 168 | + from taking the same names. |
| 169 | + |
| 170 | + On the other hand, it is also thinkable to simply forbid shadowing of prelude items as well, |
| 171 | + as defining things with the same name as std exports is not recommended anyway, and this would |
| 172 | + nicely enforce that. It would however mean that the prelude can not change without breaking |
| 173 | + backwards compatibility, which might be too restricting. |
| 174 | + |
| 175 | + A compromise would be to specialize wildcard imports into a new `prelude use` feature, which |
| 176 | + has the explicit properties of being shadow-able and using a wildcard import. `libstd`s prelude |
| 177 | + could then simply use that, and users could define and use their own preludes as well. |
| 178 | + But that's a somewhat orthogonal feature, and should be discussed in its own RFC. |
| 179 | + |
| 180 | +- Interaction with overlapping imports. |
| 181 | + |
| 182 | + Right now its legal to write this: |
| 183 | + ```rust |
| 184 | +fn main() { |
| 185 | + use Bar = std::result::Result; |
| 186 | + use Bar = std::option::Option; |
| 187 | + let x: Bar<uint> = None; |
| 188 | +} |
| 189 | + ``` |
| 190 | + where the latter `use` shadows the former. This would have to be forbidden as well, |
| 191 | + however the current semantic seems like a accident anyway. |
0 commit comments