Help making this code compile:

edit: minimal failure example

Rust play ground link: Rust Playground

use std::marker::PhantomData;
use std::iter::{Map, Enumerate};

pub struct Idx<T> {
    idx: usize,
    _t: PhantomData<T>,}
    
impl<T> Idx<T> {
    pub fn new(x: usize) -> Idx<T> {
        Idx {
            idx: x,
            _t: PhantomData::default()
        }
    }
}

pub struct Foo<T> {
    pub(crate) m_data: Vec<T>,}

impl<T> Foo<T> {
    pub fn enumerate_2(
        &self,
    ) -> Map<Enumerate<std::slice::Iter<'_, T>>, impl Fn(usize, &T) -> (Idx<T>, &T)> {
        let x = self
            .m_data
            .iter()
            .enumerate()
            .map(|(idx, x)| (Idx::new(idx), x));
        x}}

original question

m._data: Vec<T>

    pub fn enumerate_2(
        &self,
    ) -> Map<Enumerate<std::slice::Iter<'_, T>>, Fn(usize, &T) -> (Idx<T>, &T)> {
        let x = self
            .m_data
            .iter()
            .enumerate()
            .map(|(idx, x)| (Idx::new(idx), x)); 
        x}

what is the compile error ?

57 |     ) -> Map<Enumerate<std::slice::Iter<'_, T>>, Fn(usize, &T) -> (Idx<T>, &T)> {
   |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `Sized` is not implemented for `(dyn for<'r> Fn(usize, &'r T) -> (table::Idx<T>, &T) + 'static)`
note: required by a bound in `Map`

why can't you add + Sized ?

57 |     ) -> Map<Enumerate<std::slice::Iter<'_, T>>, Fn(usize, &T) -> (Idx<T>, &T) + Sized> {
   |                                                  -----------------------------   ^^^^^ additional non-auto trait
   |                                                  |
   |                                                  first non-auto trait
   |
   = help: consider creating a new trait with all of these as super-traits and using that trait here instead: `trait NewTrait: for<'r> Fn<(usize, &'r T)> + Sized {}`
   = note: auto-traits like `Send` and `Sync` are traits that have special properties; for more information on them, visit 

why not just use impl DoubleEndedIterator<Item = (Idx<K>, &T)> ?

weird issues with IntelliJ type inference when used in for loop

That Fn(...) -> ... is actually a dyn Fn(...) -> ... using outdated syntax. You don't want a trait object there1. IIRC, impl Fn(...) -> ... syntax might work in this position.

1 unless if you do want a trait object, you should do Box<dyn Fn(...) -> ...> and you'd need to box the closure, too: .map(Box::new(|(idx, x)| (Idx::new(idx), x)));

If I am reading your suggestion correctly:

    pub fn enumerate_2(
        &self,
    ) -> Map<Enumerate<std::slice::Iter<'_, T>>, dyn Fn(usize, &T) -> (Idx<T>, &T)> {
        let x = self
            .m_data
            .iter()
            .enumerate()
            .map(|(idx, x)| (Idx::new(idx), x));
        x}

results in compile error of:

57 |     ) -> Map<Enumerate<std::slice::Iter<'_, T>>, dyn Fn(usize, &T) -> (Idx<T>, &T)> {
   |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `Sized` is not implemented for `(dyn for<'r> Fn(usize, &'r T) -> (table::Idx<T>, &T) + 'static)`
note: required by a bound in `Map`

No.. my statement was that your code without the dyn is equivalent to the thing you wrote now. (Hence you're getting the same error message.) You should try impl instead of dyn; that might work.

(I'm not 100% certain impl works here, but I can't try that easily since I'm missing context from your code and I don't feel like mocking up everything myself.)

Sorry, here is minimal example:

Rust play ground link: Rust Playground

use std::marker::PhantomData;
use std::iter::{Map, Enumerate};

pub struct Idx<T> {
    idx: usize,
    _t: PhantomData<T>,}
    
impl<T> Idx<T> {
    pub fn new(x: usize) -> Idx<T> {
        Idx {
            idx: x,
            _t: PhantomData::default()
        }
    }
}

pub struct Foo<T> {
    pub(crate) m_data: Vec<T>,}

impl<T> Foo<T> {
    pub fn enumerate_2(
        &self,
    ) -> Map<Enumerate<std::slice::Iter<'_, T>>, impl Fn(usize, &T) -> (Idx<T>, &T)> {
        let x = self
            .m_data
            .iter()
            .enumerate()
            .map(|(idx, x)| (Idx::new(idx), x));
        x}}

Alright, it works if you...

turn Fn into FnMut (a bit weird; possibly due to type inference on the closure passed to map which expects an FnMut somehow making the closure only implement FnMut, not Fn)

Fix the argument type from (usize, &T) to ((usize, &T))

turn the higher-ranked bound FnMut((usize, &T)) -> (Idx<T>, &T) into a concrete bound FnMut((usize, &'a T)) -> (Idx<T>, &'a T) by introducing an explicit lifetime argument 'a.

impl<T> Foo<T> {
    pub fn enumerate_2<'a>(
        &'a self,
    ) -> Map<Enumerate<std::slice::Iter<'a, T>>, impl FnMut((usize, &'a T)) -> (Idx<T>, &'a T)>
    {
        let x = self
            .m_data
            .iter()
            .enumerate()
            .map(|(idx, x)| (Idx::new(idx), x));
        x
    }
}
1 Like

I confirm the suggested changes compiles. Thanks!

This one is a dumb mistake on my part.

This is counter intuitive to me. I would not have thought 'try replacing Fn to FnMut'.

This is even more surprising. How did you even think to try this ?

Actually, the definition of map is

    fn map<B, F>(self, f: F) -> Map<Self, F>
    where
        Self: Sized,
        F: FnMut(Self::Item) -> B,
    {
        Map::new(self, f)
    

so the Fn -> FnMut one seems reasonable now.

However, how did you come up with the 'add a lifetime bound' fix ?

Similar deal. map requires FnMut(Self::Item) -> B, so if the compiler uses that to determine the type of the closure, it makes sense that it won't implement a higher-ranked for<'a> FnMut((usize, &'a T)) -> (Idx<T>, &'a T) bound. Note that FnMut((usize, &T)) -> (Idx<T>, &T) is equivalent to / syntax sugar for for<'a> FnMut((usize, &'a T)) -> (Idx<T>, &'a T).

1 Like

Also if you don't do it, you'll get this infamously confusing error message

error[E0308]: mismatched types
  --> src/lib.rs:25:10
   |
25 |     ) -> Map<Enumerate<std::slice::Iter<'_, T>>, impl FnMut((usize, &T)) -> (Idx<T>, &T)> {
   |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected tuple `(Idx<_>, &T)`
              found tuple `(Idx<_>, &T)`

For more information about this error, try `rustc --explain E0308`.

which will make you at least guess that it's got something to do with HRTBs, at least if you've seen a message like that before.

In my case however, I've actually tried the adding-a-lifetime thing first, IIRC, then the FnMut and only very, very late I noticed the missing parentheses :laughing:. So the error message didn't help me.

1 Like

I doubt there's a reason to care in this case, but you may eventually run into a situation where you need to convince the compiler that your closure really is more general than it inferred, instead of tightening your bounds down to what the compiler inferred.

The canonical case is closures with higher-ranked lifetimes, which I think the compiler will just flat-out not infer unless you sort of force it to.

Anyway, as it might be useful to you eventually, the way you do this is to put the closure into an inference context that spells out exactly what you want -- sort of the opposite of what gave you trouble by putting the closure in map's inference sphere.

// Make me a higher-ranked closure, and a `Fn` one at that
// Syntax soup at its finest
fn hr<F: for<'a> Fn((usize, &'a T)) -> (Idx<T>, &'a T), T>(f: F) -> F { f }

// Works now
impl<T> Foo<T> {
    pub fn enumerate_2(
        &self,
    ) -> Map<Enumerate<std::slice::Iter<'_, T>>, impl Fn((usize, &T)) -> (Idx<T>, &T)> {
        let x = self
            .m_data
            .iter()
            .enumerate()
            .map(hr(|(idx, x)| (Idx::new(idx), x)));
            //   ^^^                             ^
        x
    }
}

(If you wanted the Fn but not the higher-ranked nature, you could forgo hr and just assign the closure to a variable outside of the map, and then pass the variable to map.)

Confusing in this case IMO, due to lifetime elision not penetrating the impl Trait.

It's not the impl that isn't penetrated, it's the FnMut that isn't penetrated by lifetime elision. That's always the case for Fn*-bounds or fn-pointer types. Hence, you could also simplify your

// Syntax soup at its finest
fn hr<F: for<'a> Fn((usize, &'a T)) -> (Idx<T>, &'a T), T>(f: F) -> F { f }

to

fn hr<F: Fn((usize, &T)) -> (Idx<T>, &T), T>(f: F) -> F { f }
1 Like

If you wrote just fn hr<F>(f: F) -> F { f }, I would say: okay, this is just the identity function.

Now, if you write fn hr<F: Spec>(f: F) -> F { f }, this is just some identity function for all types F that satisfies some spec Spec.

So now you have some special case here the Spec is for<'a> Fn((usize, &'a T)) -> (Idx<T>, &'a T),.

Where is the black magic happening ?

It's only helping out type inference. By writing hr(...CLOSURE), the compiler knows that the closure must implement for<'a> Fn((usize, &'a T)) -> (Idx<T>, &'a T) and it makes it happen. In particular for a HRTB closure it's impossible to annotate the closure directly to make the higher-ranked trait bound happen, so a restricted identity function like the hr function above can help out. I didn't realize before today that it can also make a difference on whether Fn is implemented or only FnMut is implemented.

Got it. This makes sense now. There is an annotation that (1) we want to do, (2) we can't do inline. So we 'useless' hr whose sole purpose is to type annotation. :slight_smile:

Yeah, my thinking was completely off for reasons I won't bother explaining. Sorry about that and thanks for the correction. (Though I wouldn't say "hence", as the where clause is not subject to return position elision, and return position impl Trait isn't a where clause. After pondering a bit, I guess you mean "they always get their own elision scope.")


Incidentally, I was idly wondering, why don't I run into this more myself? Then I thought "ah yes, I would just write impl Iterator<Item=...>."

pub fn enumerate_2(&self) -> impl Iterator<Item = (Idx<T>, &T)> { ... }

(Which is a perfectly common example of elision penetrating the impl....)

Out of curiosity, what Editor do you use ? Do you get auto type hints if you write:

for (x, y) in blah.enumerate_2() {

The main reason I switched is that with the Map<Enumerate<Iter setup, IntelliJ will auto insert type hints of

for (x "Idx<...>", y: "&T") in blah.enumerate_2() { ...

here the types in "" are shown in light blue in IntelliJ, as they are auto inferred

vim but I keep meaning to take the time to move to neovim. Or the Playground if that counts, due to being active on this forum...

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.