Skip to content

Surprising type inference on method call without explicit turbo-fish #49996

@RSSchermer

Description

@RSSchermer

Please excuse the lack of specificity in the title, it reflects my lack of understanding what is going on here.

Here's a distilled example of what I'm going for:

trait FooMut {
    type Baz: 'static;

    fn bar<'a, I>(self, iterator: &'a I)
    where
        for<'b> &'b I: IntoIterator<Item = &'b &'a Self::Baz>;
}

struct DelegatingFooMut<T>
where
    T: FooMut,
{
    delegate: T,
}

impl<T> FooMut for DelegatingFooMut<T>
where
    T: FooMut,
{
    type Baz = DelegatingBaz<T::Baz>;

    fn bar<'a, I>(self, collection: &'a I)
    where
        for<'b> &'b I: IntoIterator<Item = &'b &'a Self::Baz>,
    {
        let collection = collection.into_iter().map(|b| &b.delegate);

        self.delegate.bar(&collection)
    }
}

struct DelegatingBaz<T> {
    delegate: T,
}

(Play)

This fails to compile (on stable and nightly) with:

error[E0271]: type mismatch resolving `for<'b> <&'b I as std::iter::IntoIterator>::Item == &'b &<T as FooMut>::Baz`
  --> src/main.rs:17:23
   |
17 |         self.delegate.bar(&collection)
   |                       ^^^ expected struct `DelegatingBaz`, found associated type
   |
   = note: expected type `&&'a DelegatingBaz<<T as FooMut>::Baz>`
              found type `&&<T as FooMut>::Baz`

error[E0308]: mismatched types
  --> src/main.rs:17:27
   |
17 |         self.delegate.bar(&collection)
   |                           ^^^^^^^^^^^ expected type parameter, found struct `std::iter::Map`
   |
   = note: expected type `&I`
              found type `&std::iter::Map<<&I as std::iter::IntoIterator>::IntoIter, [closure@src/main.rs:15:53: 15:68]>`

I played around a bit and found that changing the bar implementation to the following does compile:

fn bar<'a, I>(self, collection: &'a I) where for <'b> &'b I: IntoIterator<Item= &'b &'a Self::Baz> {
    let collection: Vec<&<T as FooMut>::Baz> = collection.into_iter().map(|b| &b.delegate).collect();

    self.delegate.bar::<Vec<&<T as FooMut>::Baz>>(&collection)
}

(Play)

But only with the turbo-fish on the call to self.delegate.bar; if I remove the turbo-fish it once again fails to compile:

fn bar<'a, I>(self, collection: &'a I) where for <'b> &'b I: IntoIterator<Item= &'b &'a Self::Baz> {
    let collection: Vec<&<T as FooMut>::Baz> = collection.into_iter().map(|b| &b.delegate).collect();

    self.delegate.bar(&collection)
}

(Play)

This surprised me. From the error it seems like the compiler infers the type-parameter on the call to the delegate to be the same as the type parameter on the outer (delegating) method. I am not completely sure if this is a bug or intended behavior. If this isn't a bug I was hoping someone would perhaps be able to give me some insight into the what and why and a possible work-around.

While collecting into a Vec works as a workaround for now, I would really like to avoid having to allocate. I'm assuming that I might be able to make the original example work if I can work out a type for a turbo-fish there, but std::iter::Map seems to take the concrete type of its closure as a type parameter and I cannot figure out how to represent that in the turbo-fish type (if that's possible at all).

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-inferenceArea: Type inferenceC-enhancementCategory: An issue proposing an enhancement or a PR with one.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