14

I'm facing some strange behavior when passing an Rc<dyn Trait> as a function argument. The following code demonstrates the problem.

use std::rc::Rc;

trait Trait {}

struct Struct {}

impl Trait for Struct {}

fn function(_t: Rc<dyn Trait>) {}

fn main() {
    let n = Rc::new(Struct {});

    // ok
    let r = Rc::clone(&n);
    function(r);

    // error, why?
    // function(Rc::clone(&n));
}

If I store the Rc in a temporary variable, everything works fine. But if I try to call Rc::clone directly within the function call, I get the following error.

   |
19 |     function(Rc::clone(&n));
   |                        ^^ expected trait object `dyn Trait`, found struct `Struct`
   |
   = note: expected reference `&std::rc::Rc<dyn Trait>`
              found reference `&std::rc::Rc<Struct>`

Struct implements Trait. Why do I get this error?

pretzelhammer
  • 13,874
  • 15
  • 47
  • 98
user14278279
  • 459
  • 2
  • 9
  • Why not `n.clone()`? – tadman Sep 15 '20 at 01:37
  • 4
    I'm new to the language, but the documentation suggests that when dealing with Rc, Rc::clone is the preferred syntax to signal that one is dealing with an Rc and not a Copy. Interestingly, n.clone() works... – user14278279 Sep 15 '20 at 01:39
  • I'm trying to come up with a turbofish version that does work, but I'm having no luck. I don't think `Rc::clone` can figure out what's going on due to conflicting definitions for the return type vs. the given value, while `n.clone()` only has to deal with the return type. – tadman Sep 15 '20 at 01:46
  • 1
    I tried that as well, albeit with no luck. :-) – user14278279 Sep 15 '20 at 01:51
  • I would have thought that `Rc::clone::>(&n)` would do it, but that just made things worse. Maybe someone who knows Rust better can weigh in. – tadman Sep 15 '20 at 01:52

1 Answers1

13

This is basically just a wrinkle in the type inference rules. In order to call Rc::clone(&n), the compiler has to know what the type parameter of Rc is.

let r = Rc::clone(&n);
function(r);

At the first line, the compiler sees that Rc::clone is called with an argument of type &Rc<Struct>. It can freely pick a type for r, so it infers that the function being called should be Rc::<Struct>::clone and r is also Rc<Struct>. Moving to the next line, calling function(r) just coerces r to Rc<dyn Trait>.

function(Rc::clone(&n));

Here the compiler again has to pick a type parameter for Rc, but it doesn't have total freedom: it has to pick something that can be passed to function. So it assumes the type parameter is dyn Trait, since Rc<dyn Trait> is what function expects.

However, you cannot call Rc::<dyn Trait>::clone(&n), because while Rc<Struct> can be coerced to Rc<dyn Trait>, coercions are not transitive through referencing (you can't coerce a &Rc<Struct> to &Rc<dyn Trait>). So coercion cannot take place inside the Rc::clone(...) call.

You can make the one-line version compile by specifying the type argument as Struct using a turbofish:

function(Rc::<Struct>::clone(&n));

or by hinting to the compiler that it should not infer the type from its position as the argument of function, by adding an explicit cast:

function(Rc::clone(&n) as _);

n.clone() also works because the method resolution procedure for . leaves no room for doubt about the type parameter: it will always find Rc::<Struct>::clone because auto-dereferencing only looks at the receiver type (the type of n) and does not care about the expression's context.

Related

trent
  • 25,033
  • 7
  • 51
  • 90