Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trait object coercion supercedes deref coercion #39801

Open
abonander opened this issue Feb 14, 2017 · 14 comments
Open

Trait object coercion supercedes deref coercion #39801

abonander opened this issue Feb 14, 2017 · 14 comments
Labels
C-feature-request Category: A feature request, i.e: not implemented / a PR. I-needs-decision Issue: In need of a decision. T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@abonander
Copy link
Contributor

abonander commented Feb 14, 2017

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);
@abonander
Copy link
Contributor Author

@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.

@nikomatsakis
Copy link
Contributor

@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

@eddyb
Copy link
Member

eddyb commented Mar 6, 2017

@nikomatsakis We could try to just reverse the order, I believe both fall through if they fail.

@abonander
Copy link
Contributor Author

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.

@eddyb
Copy link
Member

eddyb commented Mar 6, 2017

@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.

@abonander
Copy link
Contributor Author

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.

@nikomatsakis
Copy link
Contributor

Yeah I'm wary of trying to change the order because of breakage.

@aturon aturon added the T-lang Relevant to the language team, which will review and decide on the PR/issue. label Mar 13, 2017
@Mark-Simulacrum Mark-Simulacrum added I-needs-decision Issue: In need of a decision. C-feature-request Category: A feature request, i.e: not implemented / a PR. labels Jul 27, 2017
@HarkonenBade
Copy link

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.

@abonander
Copy link
Contributor Author

I'm still thinking the compiler should attempt both coercions before erroring.

@wpwoodjr
Copy link

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 link

@wpwoodjr
Copy link

wpwoodjr commented May 16, 2019

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
}

Playground link

@wpwoodjr
Copy link

wpwoodjr commented May 16, 2019

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.

@ldm0
Copy link
Contributor

ldm0 commented May 2, 2020

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.

@ian-h-chamberlain
Copy link
Contributor

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::from seems like it should behave the same as a free function, but actually requires explicit conversion to slice instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-feature-request Category: A feature request, i.e: not implemented / a PR. I-needs-decision Issue: In need of a decision. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

9 participants