I'm not sure what such laser precision would buy us. The public dependency graph-based ordering is
simply intended to minimize unexpected breaking changes due to the addition of a trait method.
Let's say that itertools::Itertools and std::iter::Iterator had a method named foo. There are two ways this could come about. Either:
(a)std had it first. When the author of itertools adds it, they are surely aware of the existing method in std, and must be acutely aware that this could be a breaking change for downstream code.
(b)itertools had it first. The author could not have foreseen its inclusion in std, and std cannot afford to stop adding features just because itertools exists. But there are crates out there which use Itertools::foo, and these should continue to use it. (this is why the public dependency-based ordering prefers Itertools)
If a downstream crate wants to switch to std's foo, they can achieve this (abeit verbosely) by writing their own extension trait that calls the std method; this will take precedence over Itertools.
Note that if we ever came up with some construct where foo.bar could refer to a type or module, foo.bar::baz() would have a different possible interpretation, (foo.bar)::baz().
There’s probably no reason to think we’d ever want something like that, but just for the record.
use std::fmt::Debug;
trait Bar where Self: Debug {
fn method(&self) {
println!("Bar for {:?}", self);
}
}
trait Baz where Self: Debug {
fn method(&self) {
println!("Bar for {:?}", self);
}
}
#[derive(Debug)]
struct Foo;
impl Bar for Foo {}
impl Baz for Foo {}
fn main() {
let foo = Foo;
// for the moment the only way to work,
// but hurts writing and reading.
Bar::method(&foo);
Baz::method(&foo);
// propose this new syntax,
// nice to write and nice to read,
// especially many function calls chained together.
foo.Bar::method(); // Bar::method(&foo);
foo.Baz::method(); // Baz::method(&foo);
}
I wholeheartedly disagree that this could be such a huge problem. If I explicitly want to disambiguate a method call on a specific trait, I would argue it's even better to see the trait name, because in that context, apparently ambiguity is the biggest problem itself and so resolving it deserves the first position on the line.
However, if we still want some kind of value-level method-from-trait syntax, I'd support doing it in a way that is consistent with the already-existing type-level method-from-trait syntax, i.e. using angle brackets or perhaps simple grouping (parentheses):
<foo as Bar>.method();
or
(foo as Bar).method();
(This is possible to implement unambiguously because bare trait object syntax is deprecated and being phased out.)
The foo.bar::method way looks confusing because there is no precedent for it, and perhaps this is also why I perceive it as ugly.
Just to be clear, that means this syntax would only be available in the next edition, as bare trait objects still work in the current 2018 edition.
That said, for the case of resolution between conflicting traits, I do like the (value as Trait).method() more than full UMCS value.Trait::method().
I still think full UMCS is valuable as a pipe operation (i.e. foo |> crate::fun would be written foo . crate::fun; I think the extra space for UMCS helps give it room to breathe and makes the pipeline behavior clearer and slightly less foreign). I guess in a chain it really comes down to whether putting the extra parentheses around the first part of the chain is too disruptive or not.
Example:
give()
.me()
.a()
.foo()
. Trait::method();
// vs
(give()
.me()
.a()
.foo() as Trait)
.method();
// vs with type ascription with favorable binding behavior
give()
.me()
.a()
.foo(): Trait
.method();
// vs just making a new variable
let foo = give()
.me()
.a()
.foo();
(foo as Trait).method();
This seems like it'd be awkward, because right now the < there puts the parser into "type mode" -- which is why things like <Vec<i32>>::new() work without a turbofish, and what lets <[T]> know the [ is starting a type and not an array literal.
(Yes, it could parse up to the ., realize it was wrong, and try again, but Rust is generally good at not needing to do that, and I'd rather keep the < consistent with what it currently does, rather than introduce a new thing.)
I'm not a fan of this one because then its parse has to depend on the outcome of name resolution. And just having traits work isn't enough for some reasonable uses, like foo.Arc::clone() to emphasize that the clone is cheap, not deep.
I think that's overstated. Using foo::bar instead of bar is the usual way to path-qualify something, so it's the thing I'd automatically reach for to resolve a "this is ambiguous" problem, just as I do with multiple Results, say.
That's right, I forgot about these conflicting requirements. I'd also prefer to not introduce any such context-dependence into the parser. Which leads to my next question:
How so? (<expr> as <path>) already parses today, doesn't it? I don't think you need to represent, at a purely syntactic level, whether the path on the RHS of a cast is a type or a trait. Granted, it might be wise to change the AST for the RHS of a cast from "type" to "cast right-hand-side" (if it isn't already – I don't remember how it's currently represented in the compiler) in order not to introduce semantic confusion once paths may denote either a type or a trait, but that shouldn't in itself cause a parsing ambiguity or dependence on name resolution.
The problem is that if a method is path-qualified in the proposed way, then the identifier immediately following the dot will not be the name of the method, it will be the name of the trait instead. That is at least strange to read.
It is discouraged to call smart pointers' inherent methods using the method syntax, exactly because this is prone to ambiguity (and may lead to incorrect code accidentally compiling), due to deref coercion. And I would find it very unpleasantly surprising if the existence of deref coercions in method calls depended on whether the method name is a single identifier or a multi-segment path.
This would be really useful for macros. Right now I have this macro which makes it easy to call Clone::clone.
The problem is that it expands to $x.clone(), which means it will also work for inherent methods, or non-Clone methods. I don't want that, I want it to only work for Clone.
I could change it to use Clone::clone(&$x) instead, but then it won't work for references. So I really do need the auto-deref behavior of methods.
As an alternative, if there was a convenient way to access the auto-deref behavior (without using methods), then that would also work. And it would be more flexible, since it would work in any context, not just methods.
This doesn't solve the problem, it just shifts what name could be overloaded. Name mangling is a partial solution, but paths in names would be the complete solution.