Type mismatch in function call unless expression is stored in a variable?

I'm confused about the compiler error in this code:

use std::sync::Arc;
use std::any::Any;

struct MyStruct;
fn make() -> Arc<MyStruct> {
    Arc::new(MyStruct {})
}

fn consume(_arg: Arc<dyn Any>)
{
}

fn main() {
    let x = make();
    let y = Arc::clone(&x);
    let z: Arc<MyStruct> = y;
    consume(y);
    consume(z);
    consume(Arc::clone(&x));
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
  --> src/main.rs:19:24
   |
19 |     consume(Arc::clone(&x));
   |                        ^^ expected trait object `dyn std::any::Any`, found struct `MyStruct`
   |
   = note: expected reference `&std::sync::Arc<dyn std::any::Any>`
              found reference `&std::sync::Arc<MyStruct>`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

At L17 and L18, I have a variable of type Arc<MyStruct> that I can pass to consume(). Presumably, this is being upcast from the specific Arc<MyStruct> to the more general type Arc<dyn Any>. At L19, I have an expression of the same specific type Arc<MyStruct>, but the compiler reports a type mismatch rather than upcasting. This seems surprising. What's going on here?

It is not particularly intuitive: you need to explicit upcast to an Arc<dyn Any>. In your case:

consume(Arc::clone(&x) as Arc<dyn Any>);

Playground

It is not particularly intuitive: you need to explicit upcast to an Arc<dyn Any> .

Thanks for taking a look. I understand why that works, but why would that be necessary for the expression Arc::clone(&x) and not for the expressions y or z?

To be honest, I don't know for sure. My hypothesis is that consume forces the argument to be deduced as Arc<dyn Any>, which causes &x to be deduced as &Arc<dyn Any>. I think that, at that point, the compiler is unable to perform another pass to deduce that the inner type can be just casted as dyn Any.

This makes sense to me, but it is just my hypothesis. Maybe someone into the compiler could know the real answer.

The issue is that Arc::clone is generic, but casts only happen automatically when generics are not involved.

1 Like

@alice Interesting. Do you know of any documentation describing this in more detail?

I haven't looked in that part of the documentation for a while, but your keyword is "coercion".

Hmm. Maybe it's this? In particular, the transformation from Arc<MyStruct> to Arc<dyn Any> seems to be an instance of the coercion described there as "T => dyn Trait where T: Trait". It goes on to say:

Coercions occur at a coercion site . Any location that is explicitly typed will cause a coercion to its type. If inference is necessary, the coercion will not be performed.
...
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 .

I'm not sure if inference is involved at L19 in my example or not, but this does seem to be an instance of the last sentence there, where U is Arc<dyn Any> and T is Arc<MyStruct>.

Right, that's it. The critical sentence is here:

If inference is necessary, the coercion will not be performed.

Interesting. What does it mean here for inference to be necessary? I understand inference casually in terms of propagating types from expressions of known type, but it's not clear to me why that's needed at line 19 but not at line 18 (or 14 or 15, for that matter).

Thanks for your help!

I think the clearest way to point out the difference is that line 19 has two possible interpretations with coercions:

// coerce before clone
let new_x: Arc<dyn Any> = x;
consume(Arc::clone(&new_x));
// coerce after clone
let clone: Arc<MyStruct> = Arc::clone(&x);
let new_clone: Arc<dyn Any> = clone;
consume(new_clone);

That does help. Thanks for the help!

Basically the issue is that we are calling Arc::<T>::clone for some type T, and the compiler has this information about T:

  1. We can convert MyStruct into T.
  2. We can convert T into dyn Any.

This is not enough information to decide what T should be.

1 Like

That makes sense. Thanks again for the help!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.