Generics - Function argument that takes an Iterable of Pathlikes

I want a function that takes an Iterable of Pathlike objects as an argument. How can I make it as generic as possible?
The furthest I can go is IntoIterator<&Path>, looking at other libraries I see AsRef<Path> but I'm not sure how to combine them.

fn paths<P: AsRef<Path>, I: IntoIterator<Item = P>>(iter: I) { }

Rust Playground

6 Likes

FWIW, you can also write that with impl Trait:

fn paths(iter: impl IntoIterator<Item = impl AsRef<Path>>)
2 Likes

I guess there's no need for two type parameters:

fn paths<I>(iter: I)
where
    I: IntoIterator,
    I::Item: AsRef<Path>,
{}
4 Likes

thanks everyone!
Actually, I know about the Item type parameter, but my first attempt (that doesn't compile) was like this that didnt include the impl keyword:

fn do_something(paths: IntoIterable<Item = AsRef<Path>>)

why is the impl keyword needed here?

Because AsRef is a trait, not a type, and associated type equality bounds take a type.

2 Likes

Thx. It's idiomatic in Rust.

I suppose we should never use the redundant type parameter as an associated type in a trait bound because one type only has single determined specific associated type in its implementation?

I.e for fn paths<P: AsRef<Path>, I: IntoIterator<Item = P>>(iter: I), and the specific input [&str; 2], there is only one determined case P = &str[1], thus paths<&str, [&str; 2]> is called, and it's impossible to call paths<type_other_than_&str, [&str; 2]>.


  1. since str: AsRef<Path> and &T: AsRef<U> ↩ī¸Ž

In my opinion it depends on what you want to be turbofishable, and the most likely to need to be turbofishable (because inference doesn't cut it) should be first. There are also cases where normalization seems to fall apart, and forces your hand on whether to have another parameter or avoid one.

This is only abstract experience talking, so I'm afraid I don't have many actionable guidelines. One I do have though is that if one if your type parameters is a Fn type, put it later (than the arguments or return type, say), because it will often be a closure and thus not nameable in turbofish anyway. Or deprive your consumers of turbofish and use APIT I suppose.

2 Likes

Yeah, that happens sometimes. An unnecessary type parameter is not too bad, so I didn't want to sound like I'm fixated too much on it; in this simple case, I thought it's a trivial improvement worth making.

Ok, as an extension I have ran into a related problem. If I use the impl syntax, I cannot typehint it in my closure and I lose all type hinting in my IDE (but it mysteriously still compiles, and funnily enough the IDE can guess what the closure argument is).

I'm using the IntoParallelRefIterator trait from the rayon crate, ShootInfo is a struct.

pub fn redate_shootinfos<'a>(needs_reload: &'a impl IntoParallelRefIterator<'a, Item=&'a (impl AsRef<Path> + 'a, Arc<Mutex<ShootInfo>>)>)
{
    // do something else needs_reload
    needs_reload.par_iter().for_each(|x| {
        let (shoot_folder, shoot_info) = x;
        // i lose all type hints here
    });
}

Should I avoid using the impl syntax and use generic type parameters instead so I can type hint in my closure?

I usually recommend not using impl Trait in argument position (abbreviated APIT). It's not any more useful compared to generics, and it's actually more annoying for this reason. The fact that it behaves differently from return-position impl Trait (RPIT) can also be confusing. So I think the best is to reserve impl Trait for return types, where it serves an actual purpose that can't be served by generics.

1 Like

thanks for the tip regarding APIT!
I am still having trouble trying to convert my code to use generics.
The function argument is taking an iterable of (AsRef<Path>, Arc<Mutex<Struct>>)
But I cannot put the tuple in the generic type argument because it is not a trait.

pub fn redate_shootinfos<'a, I>(needs_reload: I)
    where I: IntoParallelRefIterator<'a>,
          I::Item: (AsRef<Path>, Arc<Mutex<ShootInfo>>)

What should I do?

You can always go back to having two type parameters, which is what I would do in this situation:

pub fn redate_shootinfos<'a, I, P>(needs_reload: I)
    where I: IntoParallelRefIterator<'a, Item=(P, Arc<Mutex<ShootInfo>>)>,
          P: AsRef<Path> {}
2 Likes