Skip to content

Commit d38d12d

Browse files
committed
RFC to require impl MyStruct to be in the same module as the definition of MyStruct
This fixes #15060 and #3785.
1 parent 648e2de commit d38d12d

File tree

1 file changed

+136
-0
lines changed

1 file changed

+136
-0
lines changed
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
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

Comments
 (0)