This idea has been brought up several times. I’m not very excited about it for several reasons.
@sinkuu’s example is a good example of how its confusing. We’d have to validate that every T
you pass to Foo
could implement Quack
if an impl is in scope, without resolving it to a specific impl. Then, it would work, but you couldn’t call any methods on Foo
without importing that impl.
What’s especially concerning, then, is that these two blocks could do totally different things:
{
use my::QuackForDuck;
foo.action();
}
{
use another::QuackForDuck;
foo.action();
}
This is especially concerning because you might import your quack impl to make a duck variable quack, without realizing you’re changing how foo behaves in that scope.
Additionally, you now have to check the imports to figure out what’s going on in every scope. You can’t just assume because you’ve seen the impl of this trait for this type before, you know what it does in this context. That seems quite bad.
We also haven’t considered how this interacts with the unnamed impls that non-orphan crates are still allowed to provide; this would have to shadow them, otherwise adding an impl to your crate is always a breaking change. But that just makes it even harder to track what’s going on.
This is all especially concerning with glob imports, which now might include an impl for types and traits that aren’t even from the same crate the glob’d module is from.
None of this is impossible - that is, we could define semantics that would still guarantee that in a given context, a single impl is found for every trait/type pair - but to me it seems much more confounding than the current behavior.
It just doesn’t seem better than the alternative - a newtype struct:
pub struct MyDuck(pub Duck);
impl Quack for MyDuck { }
fn foo() {
let my_duck = MyDuck(duck);
my_duck.quack();
let duck = my_duck.0;
}
And we could provide some ‘newtype deriving’ solution so that MyDuck
would delegate a lot of impls to Duck
without you writing it all out.