|
| 1 | +- Start Date: 2014-07-04 |
| 2 | +- RFC PR #: (leave this empty) |
| 3 | +- Rust Issue #: (leave this empty) |
| 4 | + |
| 5 | +# Summary |
| 6 | + |
| 7 | +Require "anonymous traits", i.e. `impl MyStruct` to occur only in the same module that `MyStruct` is defined. |
| 8 | + |
| 9 | +# Motivation |
| 10 | + |
| 11 | +Before I can explain the motivation for this, I should provide some background |
| 12 | +as to how anonymous traits are implemented, and the sorts of bugs we see with |
| 13 | +the current behaviour. The conclusion will be that we effectively already only |
| 14 | +support `impl MyStruct` in the same module that `MyStruct` is defined, and |
| 15 | +making this a rule will simply give cleaner error messages. |
| 16 | + |
| 17 | +- The compiler first sees `impl MyStruct` during the resolve phase, specifically |
| 18 | + in `Resolver::build_reduced_graph()`, called by `Resolver::resolve()` in |
| 19 | + `src/librustc/middle/resolve.rs`. This is before any type checking (or type |
| 20 | + resolution, for that matter) is done, so the compiler trusts for now that |
| 21 | + `MyStruct` is a valid type. |
| 22 | +- If `MyStruct` is a path with more than one segment, such as `mymod::MyStruct`, |
| 23 | + it is silently ignored (how was this not flagged when the code was written??), |
| 24 | + which effectively causes static methods in such `impl`s to be dropped on the |
| 25 | + floor. A silver lining here is that nothing is added to the current module |
| 26 | + namespace, so the shadowing bugs demonstrated in the next bullet point do not |
| 27 | + apply here. (To locate this bug in the code, find the `match` immediately following |
| 28 | + the `FIXME (#3785)` comment in `resolve.rs`.) This leads to the following |
| 29 | +```` |
| 30 | +mod break1 { |
| 31 | + pub struct MyGuy; |
| 32 | +
|
| 33 | + impl MyGuy { |
| 34 | + pub fn do1() { println!("do 1"); } |
| 35 | + } |
| 36 | +} |
| 37 | +
|
| 38 | +impl break1::MyGuy { |
| 39 | + fn do2() { println!("do 2"); } |
| 40 | +} |
| 41 | +
|
| 42 | +fn main() { |
| 43 | + break1::MyGuy::do1(); |
| 44 | + break1::MyGuy::do2(); |
| 45 | +} |
| 46 | +```` |
| 47 | +```` |
| 48 | +<anon>:15:5: 15:23 error: unresolved name `break1::MyGuy::do2`. |
| 49 | +<anon>:15 break1::MyGuy::do2(); |
| 50 | +```` |
| 51 | + as noticed by @huonw in https://github.com/rust-lang/rust/issues/15060 . |
| 52 | +- If one does not exist, the compiler creates a submodule `MyStruct` of the |
| 53 | + current module, with `kind` `ImplModuleKind`. Static methods are placed into |
| 54 | + this module. If such a module already exists, the methods are appended to it, |
| 55 | + to support multiple `impl MyStruct` blocks within the same module. If a module |
| 56 | + exists that is not `ImplModuleKind`, the compiler signals a duplicate module |
| 57 | + definition error. |
| 58 | +- Notice at this point that if there is a `use MyStruct`, the compiler will act |
| 59 | + as though it is unaware of this. This is because imports are not resolved yet |
| 60 | + (they are in `Resolver::resolve_imports()` called immediately after |
| 61 | + `Resolver::build_reduced_graph()` is called). In the final resolution step, |
| 62 | + `MyStruct` will be searched in the namespace of the current module, checking |
| 63 | + imports only as a fallback (and only in some contexts), so the `use MyStruct` is |
| 64 | + effectively shadowed. If there is an `impl MyStruct` in the file being imported |
| 65 | + from, the user expects that the new `impl MyStruct` will append to that one, |
| 66 | + same as if they are in the original file. This leads to the original bug report |
| 67 | + https://github.com/rust-lang/rust/issues/15060 . |
| 68 | +- In fact, even if no methods from the import are used, the name `MyStruct` will |
| 69 | + not be associated to a type, so that |
| 70 | +```` |
| 71 | +trait T {} |
| 72 | +impl<U: T> Vec<U> { |
| 73 | + fn from_slice<'a>(x: &'a [uint]) -> Vec<uint> { |
| 74 | + fail!() |
| 75 | + } |
| 76 | +} |
| 77 | +fn main() { let r = Vec::from_slice(&[1u]); } |
| 78 | +```` |
| 79 | +```` |
| 80 | +error: found module name used as a type: impl Vec<U>::Vec<U> (id=5) |
| 81 | +impl<U: T> Vec<U> |
| 82 | +```` |
| 83 | + which @Ryman noticed in https://github.com/rust-lang/rust/issues/15060 . The |
| 84 | + reason for this is that in `Resolver::resolve_crate()`, the final step of |
| 85 | + `Resolver::resolve()`, the type of an anonymous `impl` is determined by |
| 86 | + `NameBindings::def_for_namespace(TypeNS)`. This function searches the namespace |
| 87 | + `TypeNS` (which is <i>not</i> affected by imports) for a type; failing that it |
| 88 | + tries for a module; failing that it returns `None`. The result is that when |
| 89 | + typeck runs, it sees `impl [module name]` instead of `impl [type name]`. |
| 90 | + |
| 91 | + |
| 92 | +The main motivation of this RFC is to clear out these bugs, which do not make |
| 93 | +sense to a user of the language (and had me confused for quite a while). |
| 94 | + |
| 95 | +A secondary motivation is to enforce consistency in code layout; anonymous traits |
| 96 | +are used the way that class methods are used in other languages, and the data |
| 97 | +and methods of a struct should be defined nearby. |
| 98 | + |
| 99 | +# Detailed design |
| 100 | + |
| 101 | +I propose three changes to the language: |
| 102 | + |
| 103 | +- `impl` on multiple-ident paths such as `impl mymod::MyStruct` is disallowed. |
| 104 | + Since this currently suprises the user by having absolutely no effect for |
| 105 | + static methods, support for this is already broken. |
| 106 | +- `impl MyStruct` must occur in the same module that `MyStruct` is defined. |
| 107 | + This is to prevent the above problems with `impl`-across-modules. |
| 108 | + Migration path is for users to just move code between source files. |
| 109 | + |
| 110 | +# Drawbacks |
| 111 | + |
| 112 | +Static methods on `impl`s-away-from-definition never worked, while non-static |
| 113 | +methods can be implemented using non-anonymous traits. So there is no loss in |
| 114 | +expressivity. However, using a trait where before there was none may be clumsy, |
| 115 | +since it might not have a sensible name, and it must be explicitly imported by |
| 116 | +all users of the trait methods. |
| 117 | + |
| 118 | +For example, in the stdlib `src/libstd/io/fs.rs` we see the code `impl path::Path` |
| 119 | +to attach (non-static) filesystem-related methods to the `Path` type. This would |
| 120 | +have to be done via a `FsPath` trait which is implemented on `Path` and exported |
| 121 | +alongside `Path` in the prelude. |
| 122 | + |
| 123 | +It is worth noting that this is the only instance of this RFC conflicting with |
| 124 | +current usage in the stdlib or compiler. |
| 125 | + |
| 126 | +# Alternatives |
| 127 | + |
| 128 | +- Leaving this alone and fixing the bugs directly. This is really hard. To do it |
| 129 | + properly, we would need to seriously refactor resolve. |
| 130 | + |
| 131 | +# Unresolved questions |
| 132 | + |
| 133 | +None. |
| 134 | + |
| 135 | + |
| 136 | + |
0 commit comments