Description
I stumbled upon this issue by accident, as I noticed that using a symbol that has been imported in multiple glob statements does not result in an error:
mod foo {
pub fn p() { println("foo"); }
}
mod bar {
pub fn p() { println("bar"); }
}
mod baz {
use foo::*;
use bar::*;
#[main]
fn my_main() {
p();
}
}
A simple oversight, I thought, and dove into resolve to fix the bug. Then I noticed that not even single imports are checked for conflicts:
mod foo {
pub fn p() { println("foo"); }
}
mod bar {
pub fn p() { println("bar"); }
}
mod baz {
use foo::p;
use bar::p;
#[main]
fn my_main() {
p();
}
}
I assumed that the reason for this was that currently both glob and single imports are flattened into a hashmap per module and tried to introduce a 2-level import scheme that allows duplicates only in glob imports but not in single imports.
And then I noticed that it's possible to export glob imports from a crate, making that scheme impossible. Even worse: this makes the global API of a crate dependent on the vigilance of the developer to not export multiple symbols with the same name, as a simple rearranging of use statements in the source code could break existing users of the crate.
So, there are four possible ways to go about this problem:
-
Don't change anything. I.e. don't check for duplicate imported symbols, neither single imports nor glob imports. (Maybe add a lint, but that would probably have to be turned of in many cases, e.g.
libstd
is full of duplicate glob imports). -
Disallow duplicate imports, even when glob importing. This is IMO not workable.
-
Disallow exporting glob imports from crates, making the aforementioned 2-level duplicate checking possible (i.e. disallow duplicate single imports, disallow used duplicate glob imports, allow unused duplicate glob imports).
-
A variant on 3): allow exporting glob imports, implement the 2-level scheme for imports that are not visible from outside the crate, but disallow any duplicate imports otherwise.
-
or 4) would be my preferred solution, but would incur a lot of work, both in implementing the scheme and restructuring existing code. 1) is the most realistic solution, but I personally don't really like it, as Rust is all about safety after all.
Fun example at the end:
mod foo {
pub fn p() { println("foo"); }
}
mod bar {
pub fn p() { println("bar"); }
}
mod baz {
use bar::p;
use foo::*;
#[main]
fn my_main() {
p();
}
}
Here, the use foo::*;
is marked as being unused, even though when running the program, foo::foo()
is actually the implementation of foo()
that is used.