How to reduce number of generic parameters of this function

I have this generic function:

pub fn join_seq<'a, X, Head, Tail, HeadItem, TailItem>(
    head: Head, tail: Tail
) -> impl Iterator<Item = &'a X> + 'a
where
    Head: IntoIterator<Item = &'a HeadItem> + 'a,
    Tail: IntoIterator<Item = &'a TailItem> + 'a,
    HeadItem: AsRef<X> + ?Sized + 'a,
    TailItem: AsRef<X> + ?Sized + 'a,
    X: ?Sized + 'a,
{
    head.into_iter().map(|v| v.as_ref()).chain(
        tail.into_iter().map(|v| v.as_ref())
    )
}

Is it possible to reduce the number of generic parameters that it requires? Specifically I was wondering if I can get rid of HeadItem and TailItem.

I tried using impl IntoIterator in function argument positions, see join_seq2 in this playground link. Then I ran into E0632 which says:

Either all generic arguments should be inferred at the call site, or
the function definition should use an explicit generic type parameter
instead of impl Trait.

Thanks in advance.

A somewhat hacky (but nevertheless safe) approach is to use a macro:

macro_rules!  join_seq {
  ($head:expr, $tail:expr) => {
    (head.into_iter().map(|v| v.as_ref()).chain(
        tail.into_iter().map(|v| v.as_ref())
    ))
  };
}

Here's a version with three type parameters instead of five.

// Our own `Into`, just for reference conversion via `AsRef`.
// Sadly it has to be public
pub trait Conv<T> {
    fn conv(self) -> T;
}

impl<'a, Item, X> Conv<&'a X> for &'a Item
where
    Item: AsRef<X> + ?Sized + 'a,
    X: ?Sized + 'a,
{
    fn conv(self) -> &'a X {
        self.as_ref()
    }
}

pub fn join_seq2<'a, X, Head, Tail>(
    head: Head,
    tail: Tail,
) -> impl Iterator<Item = &'a X> + 'a
where
    Head: IntoIterator + 'a,
    Tail: IntoIterator + 'a,
    X: ?Sized + 'a,
    Head::Item: Conv<&'a X>,
    Tail::Item: Conv<&'a X>,
{
    head.into_iter().map(Conv::conv).chain(
        tail.into_iter().map(Conv::conv)
    )
}
// ...
    let it = join_seq2::<str, _, _>(["A"], a.iter().take(1));

Playground.

1 Like

Nice. I love how you applied the FTSE.

Having to expose the Conv trait is a bit of extra hassle for the user code but can be dealt with.

By the way, what will take Rust to accept this:

where
  Head: IntoIterator + 'a,
  Head::Item: &'a AsRef<X> + 'a,
  X: ?Sized + a,

Thanks again.

This could be a practical solution. But I am learning generics so I'd like to stay away from macros in this particular case. Sorry for that I didn't make this clear in my original post.

Nevertheless, I still appreciate your input.

The lifetimes can be made a bit more flexible:

  • the items don't need to be &'a Item, they could be longer-lived references
  • the iterators don't need to outlive 'a, they could be shorter-lived than the lifetime of the contained references
pub trait Conv<T> {
    fn conv(self) -> T;
}

impl<'a, 'b: 'a, Item, X> Conv<&'a X> for &'b Item
where
    Item: AsRef<X> + ?Sized,
    X: ?Sized,
{
    fn conv(self) -> &'a X {
        self.as_ref()
    }
}

pub fn join_seq2<'a: 'b, 'b, X, Head, Tail>(
    head: Head,
    tail: Tail,
) -> impl Iterator<Item = &'a X> + 'b
where
    Head: IntoIterator + 'b,
    Tail: IntoIterator + 'b,
    X: ?Sized + 'a,
    Head::Item: Conv<&'a X>,
    Tail::Item: Conv<&'a X>,
{
    head.into_iter().map(Conv::conv).chain(
        tail.into_iter().map(Conv::conv)
    )
}

Another difference: Your Conv implementation had explicit lifetime bounds on X and Item that could actually be left out because they're implied.

2 Likes

Maybe the ability to accept something like

Head: IntoIterator<Item=&'a (impl AsRef<X> + 'a)>

or the like. Which would function something like a generic associated type, so perhaps it would only be available in a trait.

1 Like