A couple of points for you, that I hope you can use to steel-man your proposal against my side:
First, there's an edge case I'd like you to describe and explain how you handle, because while I don't think it's evidence for or against your proposal, it does change the semantics of Rust in a possibly surprising fashion.
Imagine I have a crate bar
, with an exported function: pub fn flavour(day: chrono::Weekday) -> Flavour
. bar
thus depends on the crate chrono
, in order to refer to chrono::Weekday
. Assume that bar
does not re-export anything from chrono
; that means that in today's Rust, for crate foo
to call bar::flavour()
, it needs to have chrono
as a dependency, so that Rust knows how enum Weekday
is defined. How do you intend to handle this?
There's two reasonable answers: one is to say that foo
can call bar::flavour(.Mon)
, because while foo
does not depend on chrono
, bar
does - and we'll treat this as-if bar
re-exported enum Weekday
in order to make this work. The other is to say that chrono::Weekday
is not visible in crate foo
, and thus that it does not compile until foo
depends on chrono
.
Secondly, the alternative way to deal with the original problem is to extend glob imports, rather than type inference, and I'd like you to call this out as an alternative option (even if you don't expand it).
You can treat today's import machinery as having a name in one of three states:
- Not imported; you have to use a path to refer to it unambiguously. E.g.
::chrono::Weekday::Mon
.
- Explicitly imported;
Mon
becomes unambiguously ::chrono::Weekday::Mon
, and if another use
statement tries to explicitly import another Mon
, Rust generates an error, because there's only ever one "strong" import for a name.
- Glob imported; this imports things if there is no explicit import for the same name, so
use ::chrono::Weekday::*
will define Mon
as ::chrono::Weekday::Mon
, but only if nothing else tries to import a Mon
into scope. If two glob imports both import the same name, then the name is treated as "not imported".
The weaker form of the extension is to say that when I do use foo
, this not only explicitly imports foo
(as today), but also recursively glob imports foo::*
, using the glob import rules for ambiguity.
The stronger form is to extend the "unambiguous" versus "ambiguous" with resolution rules; you could track the "depth" of the import to work out which one wins. In this case, use chrono::Weekday::Mon
has depth 0 for Mon
, because there's no globbing or implicit imports involved. use chrono::Weekday::*
has depth 1 for Mon
, because it's a glob import. use chrono::Weekday
has depth 2 for Mon
(one level to make it match the glob import, one because Rust has inferred the import). And use chrono
has depth 3 for Mon
(one level to get toWeekday
, then the other two to match use chrono::Weekday
). A name is ambiguous if there are two definitions of it at the same level; otherwise, the smallest depth number wins (so depth 0 beats depth 1, depth 3 beats depth 4).
This is a very different path to the same intended effect; it'll have different tradeoffs to the one based on type inference, and it's thus a strong alternative to compare against in order to explain why the type inference based option is better (are ambiguities better-handled in the type inference version? Is a change to the types of a function less likely to result in surprising compiler behaviour? etc, etc)