Skip to content

Trait object coercion supercedes deref coercion #39801

Open

Description

@abonander

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);

Activity

  1. abonander commented on Mar 6, 2017

    @abonander
    ContributorAuthor

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

  2. nikomatsakis commented on Mar 6, 2017

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

  3. eddyb commented on Mar 6, 2017

    @eddyb
    Contributor

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

  4. abonander commented on Mar 6, 2017

    @abonander
    ContributorAuthor

    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.

  5. eddyb commented on Mar 6, 2017

    @eddyb
    Contributor

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

  6. abonander commented on Mar 6, 2017

    @abonander
    ContributorAuthor

    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.

  7. nikomatsakis commented on Mar 8, 2017

    @nikomatsakis
    Contributor

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

  8. HarkonenBade commented on Apr 20, 2018

    @HarkonenBade

    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.

  9. abonander commented on Apr 20, 2018

    @abonander
    ContributorAuthor

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

  10. wpwoodjr commented on May 14, 2019

    @wpwoodjr

    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

  11. wpwoodjr commented on May 16, 2019

    @wpwoodjr

    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

  12. wpwoodjr commented on May 16, 2019

    @wpwoodjr

    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.

  13. ldm0 commented on May 2, 2020

    @ldm0
    Contributor

    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.

  14. ian-h-chamberlain commented on Feb 15, 2023

    @ian-h-chamberlain
    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.

  15. added
    A-coercionsArea: implicit and explicit `expr as Type` coercions
    A-dyn-traitArea: trait objects, vtable layout
    T-typesRelevant to the types team, which will review and decide on the PR/issue.
    on Dec 21, 2024
  16. hkBst commented on Nov 7, 2025

    @hkBst
    Member

    Function arguments are coercion sites but MyType::from is not a normal function. Rather it is an ambiguous reference to a whole set of functions (one for each From impl). Consequently explicitly choosing the right one from the set helps coercion to kick in: it works with let _ = <MyType as From<&[u8]>>::from(b""); but you probably want MyType::from(&b""[..])

    The original test case (updated):

    use std::sync::Arc;
    
    trait Foo {}
    
    struct Bar;
    
    impl Foo for Bar {}
    
    fn takes_foo(foo: &dyn Foo) {}
    
    fn main() {
        let foo: Arc<dyn Foo> = Arc::new(Bar);
        takes_foo(&*foo); // remove * for error
        let foo: Box<dyn Foo> = Box::new(Bar);
        takes_foo(&*foo); // remove * for error
    }

    Box<dyn Foo> could have its own impl Foo which would lead methods of Foo called in takes_foo to be different from the ones resulting from coercing.

    Given this analysis I'm inclined to agree with @ldm0 that this is at worst a diagnostic issue.

    I'm not sure how difficult it would be to make these cases suggest a possible fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-coercionsArea: implicit and explicit `expr as Type` coercionsA-dyn-traitArea: trait objects, vtable layoutC-feature-requestCategory: A feature request, i.e: not implemented / a PR.I-needs-decisionIssue: In need of a decision.T-langRelevant to the language teamT-typesRelevant to the types team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions