Type inference in block tail expression

Consider this snippet:

use std::sync::Arc;

trait Foo {}

trait FooStorage {
    fn foo_cloned(&self) -> Arc<dyn Foo>;
}

struct FooStorageImpl<T: Foo> {
    foo: Arc<T>,
}

impl<T: Foo + 'static> FooStorage
    for FooStorageImpl<T>
{
    fn foo_cloned(&self) -> Arc<dyn Foo> {
        let ret = Arc::clone(&self.foo);
        ret
    }
}

If I try to remove the binding, returning the expression directly like this...

    fn foo_cloned(&self) -> Arc<dyn Foo> {
        Arc::clone(&self.foo)
    }

...the compiler rejects it with the following error:

error[E0308]: mismatched types
  --> src/lib.rs:17:20
   |
13 | impl<T: Foo + 'static> FooStorage
   |      - this type parameter
...
17 |         Arc::clone(&self.foo)
   |                    ^^^^^^^^^ expected trait object `dyn Foo`, found type parameter `T`
   |
   = note: expected reference `&std::sync::Arc<dyn Foo>`
              found reference `&std::sync::Arc<T>`
   = help: type parameters must be constrained to match other types
   = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters

I found that it compiles if I use Arc::<T>::clone(...) or <Arc<T> as Clone>::clone(...) but I would like to understand why the type inference succeeds if a binding is introduced and immediately returned.

Thanks!

I think this is because of the interaction between method resolution and type inference. "Method resolution" refers to the compiler's algorithm for taking a short-hand syntax like foo.clone() or Arc::clone(&foo) and mapping it to a specific clone method (e.g. <Arc<T> as Clone>::clone).

Method resolution in Rust as I understand it is fairly simplistic: Starting from the types known at the call site, the compiler searches for a method with the correct name, and stops searching as soon as it finds one. It always chooses the first method in its search order, even if that one fails type-check while some later method would pass.

I believe method resolution (unlike type inference) also works in a single forward pass through the code: Each method call is resolved based on type information from the code up to that point; it can't use types that are inferred from code that comes later in the function.

So in the original code, apparently it finds <Arc<dyn Foo> as Clone>::clone first, because it knows at this point that the expected return type is Arc<dyn Foo>. In the modified code, the expected type of the ret binding isn't known at this point, so the method resolver has to start from the argument type, &Arc<T>, so it correctly finds <Arc<T> as Clone>::clone.

3 Likes

Oh, I was not aware that method resolution would stop at the first match, it makes more sense now.

Thanks!

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.