Trait object coercion supercedes deref coercion
This seems like it should work: Playground link
use std::sync::Arc;
trait Foo {}
struct Bar;
impl Foo for Bar {}
fn takes_foo(foo: &Foo) {}
fn main() {
let foo: Arc<Foo> = Arc::new(Bar);
takes_foo(&foo);
}
However, it seems the compiler is attempting to coerce &Arc<Foo> to a trait object before attempting deref coercion, which gives this unintuitive error:
rustc 1.15.1 (021bd294c 2017-02-08)
error[E0277]: the trait bound `std::sync::Arc<Foo>: Foo` is not satisfied
--> <anon>:13:15
|
13 | takes_foo(&foo);
| ^^^^ the trait `Foo` is not implemented for `std::sync::Arc<Foo>`
|
= note: required for the cast to the object type `Foo`
Adding a deref-reref seems to fix the issue, but it's definitely a papercut:
takes_foo(&*foo);
@nikomatsakis @nrc Wasn't sure who to ping here, just looking for a judgement on whether this behavior is intended or if it's something that can be improved.
Austin Bonander at 2017-03-06 01:30:07
@abonander hmm yes I agree this is suboptimal. I'm not sure what fix, if any, to do though. For one thing, if we made changes here, it might break code that is using the existing behavior -- and it seems like we'd be trying to make a very narrow targeted change here? (i.e., to detect the case where the trait object would be the same as the result of the deref).
cc @rust-lang/lang
Niko Matsakis at 2017-03-06 19:30:00
@nikomatsakis We could try to just reverse the order, I believe both fall through if they fail.
Eduard-Mihai Burtescu at 2017-03-06 19:38:36
Well the problem that I have isn't that trait object coercion supersedes deref coercion, it's that the compiler doesn't try deref coercion when trait object coercion fails. If we got that working, then it wouldn't break anything, it would just make more code compile that didn't before.
Austin Bonander at 2017-03-06 19:38:48
@abonander It's tricky, because it fails later, and that's required so it doesn't fail if more inference is required, although we could compromise a bit more here? Still, I'm not too happy about more hacks.
Eduard-Mihai Burtescu at 2017-03-06 19:39:51
Reversing the order I think would introduce subtle breakage because if you have
impl Foo for Box<Foo>then it will hit the inner impl first, which is unintuitive.Austin Bonander at 2017-03-06 19:42:11
Yeah I'm wary of trying to change the order because of breakage.
Niko Matsakis at 2017-03-08 17:17:51
I was wondering if any further progress had been made on this? I'm encountering several examples of this defect in code i've been constructing that is using custom smart pointers, and the added derefs are clunky and unhelpful.
Tom at 2018-04-20 23:23:09
I'm still thinking the compiler should attempt both coercions before erroring.
Austin Bonander at 2018-04-20 23:29:29
In addition to the above example, methods can normally be called by passing self as an argument, but not in this case where a trait object would require deref coercion:
trait Foo { fn foo_method(&self); } struct Bar; impl Foo for Bar { fn foo_method(&self) {} } fn main() { let foo: &Foo = &Bar{}; foo.foo_method(); // OK Foo::foo_method(foo); // OK let foo: Box<Foo> = Box::new(Bar); foo.foo_method(); // OK Foo::foo_method(&foo); // Error }This seems like an ergonomics consistency issue/bug.
Playground linkBill Wood at 2019-05-14 02:17:07
Here's the same issue, this time passing a boxed Bar to a generic parameter which implements Foo:
trait Foo {} struct Bar; impl Foo for Bar {} fn takes_foo_generic<T: Foo>(foo: &T) {} fn main() { let bar = Bar; let boxed_bar = Box::new(Bar); takes_foo_generic(&bar); // OK takes_foo_generic(&boxed_bar); // error[E0277]: the trait bound `std::boxed::Box<Bar>: Foo` is not satisfied }Bill Wood at 2019-05-16 13:57:23
I think these inconsistencies are confusing, especially for new users, and especially in the example of Box<T> which otherwise acts pretty much like T.
Bill Wood at 2019-05-16 15:35:21
I guess this is the expected behaviour? Check RFC401
Note that we do not perform coercions when matching traits (except for receivers, see below). If there is an impl for some type U, and T coerces to U, that does not constitute an implementation for T. For example, the following will not type check, even though it is OK to coerce t to &T and there is an impl for &T:
struct T; trait Trait {} fn foo<X: Trait>(t: X) {} impl<'a> Trait for &'a T {} fn main() { let t: &mut T = &mut T; foo(t); //~ ERROR failed to find an implementation of trait Trait for &mut T }It's more like a diagnostic issue to me.
Donough Liu at 2020-05-02 22:00:00
Is this the same issue? It seems related, but slightly different since in this case the trait is generic, rather than the function?
struct MyType; impl From<&[u8]> for MyType { fn from(_: &[u8]) -> Self { Self } } fn takes_slice(_: &[u8]) {} fn main() { takes_slice(b""); let _ = MyType::from(b""); //~ ERROR the trait bound `MyType: From<&[u8; 0]>` is not satisfied }At least in this case, there is a nice diagnostic
help: the trait `From<&[u8]>` is implemented for `MyType`. (playground).Still, this feels a bit unexpected since
From::fromseems like it should behave the same as a free function, but actually requires explicit conversion to slice instead.Ian Chamberlain at 2023-02-15 22:50:12