So, say we have an impl std::io::Write, we have to import
use std::io::Write;
Before writeln! to it. However, if we have a dyn std::io::Write, writeln! will work without std::io::Write in the scope.
I guess this is because impl Trait is some syntax suger that resolves to adding an anonymous type parameter. But is there any space of improvement for the compiler? What will be the problems / difficulties if we want impl Traits to work as dyn Traits?
this is a known but not formally specified behavior.
trait objects (&dyn Trait, Box<dyn Trait>, etc) are indeed special cased by the compiler for method resolution without the need to import the trait into scope.
after some searching I find this old report:
and this earlier related explanation:
I don't really know how the design decision is made. there's some argument about semver breakage in the linked issue, the argument goes like this: it should be possible for a public function to change its return type from an opaque type into a concrete type implementing the trait without breaking the user code.
but I'm not entirely convinced. when an opaque type is replaced with a concrete type, it could cause a method resolution conflicts anyway, forcing the caller to import the trait to use the opaque type does not prevent breaking the user code at all!
personally I think the opaque type impl Trait should have the same special treatment just like a generic type with bound T: Trait.
In my understanding, the impl Trait parameter is syntax sugar for the generic T:Trait
US
fn test(a; impl Shape)
Is syntax sugar/equal to
fn test<T: Shape>(a: T)
1 impl Trait (different Trait name) is independent generic
An impl Traitneeds to import its Trait in its definition code and all code that calls it.dyn Trait` only needs to import the Trait in its definition code, the caller does not need to import the Trait again
As for why impl Trait needs to import its Trait in all callers, I don't know what the benefit is. If it's to make it easier to track where the definition is located, just look at the import line of the code that uses the Trait and you'll be sure the Trait definition also exists or is imported there, because there is already a requirement that the Trait must be imported in the definition of code, just like dyn Trait. Or right click to go to definition in editor
Technically, it should be possible to create an impl Trait parameter without needing to import the Trait in the caller, like dyn Trait. The Trait name is already known, like a: impl Shape, the Trait is clearly Shape. The compiler simply looks for the Trait Shape in the data during the compilation process, and then resolves it to that Trait. Just like dyn Trait, first the compiler generates the vtable based on Trait that is in scope, then when compiling the caller, the compiler just passes the vtable address. In impl Trait would look like, the compiler has a table of list of all Traits during compilation process, when compiling the caller, the compiler uses the table to find which Trait based on the type info of the param impl Trait since it is known at compile time
In argument position (only), yes. Not in return position. In that case it's an opaque alias to some other concrete type.[1]
The two replies above me are talking about each of these - are talking about different things. I'm unsure which you were asking about.
It effects method resolution. Import the Borrow trait in code using Rc<RefCell<_>> and feel the pain. It would also cause more ambiguity errors, so you'd need a fully qualified path call intead of .method().
Generics avoiding dynamic dispatch is intentional, and not all traits are vtable compatible. Global analysis is also undesirable.
There's the param code Rc<RefCell<impl Shape>>. In the type info, just look at the Shape trait ID and find the trait by accessing the table with that ID index. Once you find the Trait, just generate the static dispatch code as usual
I didn't say dynamic dispatch, because I said at compile time, not generating a table for runtime calls. But the table for Trait lookup in compilation process. So it can find and generate static dispatch without importing Trait in all callers again, just import it once in the code definition like dyn Trait
It could desugar to: <Rc<RefCell<Shape>> as Borrow<Rc<RefCell<Shape>>>>::borrow(&thing), <Rc<RefCell<Shape>> as Borrow<RefCell<Shape>>>::borrow(&thing) or <RefCell<Shape>>::borrow(&thing), all of which are functions that exist, but only the last is what you mean.
If you don't import std::borrow::Borrow, then the first two of those are considered out-of-scope (they exist, and can be named using qualified forms like <Rc<RefCell<Shape>> as std::borrow::Borrow<Rc<RefCell<Shape>>>>::borrow(&thing) if you need them, instead of <RefCell<Shape>>::borrow), and the compiler thus knows that only the third form could be what you meant by thing.borrow().
The short version of @farnz's example is that every type implements the Borrow trait, so rc.borrow() chooses &rc (a &Rc<_>) as the receiver for the trait method Borrow::borrow instead of choosing &*rc (a &RefCell) for the inherent method RefCell::borrow.
(I've seen this cause problems many times and my advice is to not import that trait if you're working with RefCells. People sometimes automatically import it during development without understanding the implications, because the compiler suggests it[1] it if you call .borrow() on a String or whatever.)
It's not create what do you even mean by that. Do you want that to work only on impl Trait, but not for concrete types? That's possible, but would be even more inconsistent. Do you want for that to work for concrete types, too? That would be an umitigated disaster.
Eveyone may add trait with any name for any type — and implement it, too. If you implement automatic resolution concrete types then change in any crate may break any other crate. That's clearly unaccaptable. Doing this for impl Trait but for not concrete types is possible, but would add something that would look strange for many developers who don't think about implications.
Note that there are fundamental difference between dyn Trait and impl Trait: dyn Trait is one, fixed, type, while impl Type may hide unlimited number of different types. Worse (but related): dyn Trait may only ever cover one Trait (if you need to combine two you need to give them separate name), but impl Trait is a shortcut for T: Trait and that full form permist combination of many traits that may conflict.
The borrow() conflict arises because the caller itself imports use std::borrow::Borrow. The ambiguity arises from the caller's choice, not from any trait leaking from the callee
What I propose is this:
If there is fn test(a: impl Shape), the caller doesn't need to use Shape just to call test(a)
So the problem that already exists above, but that problem has nothing to do with the proposal to remove the need to import traits in the caller just to call a function with a parameterized impl Trait
You seem to have misunderstood that my method is still scope based like the current method, but I already said it is exact ID lookup based, so it will not be affected by the scope because it never searches the scope in the first place
But my understanding about the path was incorrect, now there will be no path leading to the declaration, so it will be harder to track the dependency code without IDE that has "go to definition", like reading from Github, so it is not good