How to make function take different kinds of sequences of the same type?

I want a function to be able to pass a vector (or slice), or a sequence made on the fly:

  • case 1: my_func(&item_ids_vec)
  • case 2: my_func([extra_item_id].into_iter().chain(item_ids_vec.iter())

What are the options?

  • items: &[ItemId] won't work with case 2.
  • items: impl Iterator<Item=ItemId> won't work, because it allows only one particular impl.
  • dyn Iterator<Item=ItemId> might be slow, and causes some weird compliations errors, I can't find a way to fix.
  • dyn Iterator<Item=&ItemId> -- compiler is unhappy. Says lifetimes are unstable.
  • in case 2, compose a new vector before calling (or reuse one to reduce allocations). Argh, ugly!

Any ideas how to write this kind of function?

A sketch on Rust playground:

#[derive(Debug)]
struct ItemId(usize);

enum Package {
    Combo { first: ItemId, other: Vec<ItemId> },
    Simple { items: Vec<ItemId> }
}

fn process_sequence(items: &dyn Iterator<Item=ItemId>) {
    for i in items {
        println!("item {i:?}");
    }
}

fn main() {
    let packs = vec![
        Package::Simple { items: vec![ItemId(1), ItemId(2), ItemId(3)] },
        Package::Combo { first: ItemId(10), other: vec![ItemId(12), ItemId(13), ItemId(14)] }
    ];
    
    for p in packs {
        match p {
            Package::Simple { items } => process_sequence(&items.into_iter()),
            Package::Combo { first, other } => {
                process_sequence(&[first].into_iter().chain(other))
            }
        }   
    }
}

You can use generics.

Notice that depending on what you wnat to do with T you might need to bind it to other traits, like I did with Debug.

use core::fmt::Debug;

fn process_sequence<I, T>(items: I) where I: Iterator<Item = T>, T: Debug {
    for i in items {
        println!("item {i:?}");
    }
}
2 Likes

Oh, now I got it. Actually, my initial version with impl was the same generic as you suggest, and I mistakenly thought it must be the same thing. But in fact, one can call it with different types from the outside, and it will compile to 2 different functions.

So I just edited my one better:

fn process_sequence(items: impl Iterator<Item=ItemId>) {
    for i in items {
        println!("item {i:?}");
    }
}

...
            Package::Simple { items } => process_sequence(items.into_iter()),
            Package::Combo { first, other } => {
                process_sequence(&mut [first].into_iter().chain(other))
            }

I only can't understand why compiler wants &mut [first] there.

1 Like

Huum, I'm not sure why its asking that, can you post the compiler error message ?

Actually, there are two problems, the first is that its thinking you're trying to pass an immutable reference to a Chain iterator.

Doing this it will be clear you want to create an iterator out of a slice of ItemId:

process_sequence((&[first]).into_iter().chain(other))

However since you're using a slice, that will be an iterator of &ItemId which would cause another problem because your process_sequence function is not expecting an iterator of &ItemId.

Just use an array istead of a slice:

process_sequence([first].into_iter().chain(other))

If you really want to pass ItemId arround as a reference you'll need to change a few things.

1 Like

A follow-up: after returning to the problem, I noticed

  1. I couldn't consume the original structure, hence references and endless compile errors.
  2. The first element of the sequence had to be distinguished always.

So I just decided to separate the first element in another argument:

fn process_sequence(first: &ItemId, rest: &[ItemId]) {
...
}

process_sequence(&simple[0], &simple[1..]);
process_sequence(&first, &other);

The signature isn't pretty, but the function is only used to de-duplicate some heavy lifting code, just these 2 places, so this will suffice.

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.