Mali Remorker
Mali Remorker

Reputation: 1276

Passing an iterable, or a collection to a function

Consider this pseudo-pseudocode. I have quite some difficulties with the keyarg argument below,

type MapSet<K,V> = HashMap<K,HashSet<V>>;

fn contract<K,V,V1>(left: &MapSet<K,V>, right: &MapSet<V,V1>, 
                    out: &mut MapSet<K,V1>,
                    keyarg: Option(__something_iterable__) {

    let keys = match keyarg {
        Some(keys) => {
            keys
        },
        None => {
           left.keys()
        },
    }

    for &k in keys {
        // ... do stuff ...
    }
}

The function is used sometimes like this,

let x: MapSet<u32,String>;
let y: MapSet<String,u32>;
let out: MapSet<u32,u32>;
let kk: HashSet<u32>;

// ... snip ...

contract(x,y,out,kk);

And, at other times like that,

let x: MapSet<u32,String>;
let y: MapSet<String,u32>;
let out: MapSet<u32,u32>;
contract(x,y,out,None);

In other words, I am sometimes using keyarg argument to pass an Option with a reference to a HashSet containing the keys I want to iterate over and at other times I want to iterate over all the keys contained in left argument, so the keyarg becomes simply None.

But, so far I have always ended up in a problem with the match which complains that None leads to Keys object and Some branch to HashSet (type mismatch error).

My question is how to define the keyarg argument so that the branches of match are compatible with each other. That is, I want to express the fact that the variable keys is just something one can iterate over to the compiler.

Upvotes: 2

Views: 433

Answers (1)

kmdreko
kmdreko

Reputation: 60493

Unless keyarg will always match your MapSet iterator, and in your pseudo code it doesn't, you'll need keys to be a trait object. The iterator for MapSet<K,V> has the trait Iterator<Item = &K>, so you can match it into a Box like so:

let keys: Box<dyn Iterator<Item = &K>> = match keyarg {
    Some(keys) => Box::new(...),
    None => Box::new(left.keys()),
}

As far as determining what keyarg should be, the generic way would be to accept any matching iterable like so:

fn contract<'a, K, V, V1, I: IntoIterator<Item = &'a K>>(
                       // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    left: &'a MapSet<K, V>,
    right: &MapSet<V, V1>,
    out: &mut MapSet<K, V1>,
    keyarg: Option<I>,
) {    
    let keys: Box<dyn Iterator<Item = &K>> = match keyarg {
        Some(keys) => Box::new(keys.into_iter()),
        None => Box::new(left.keys()),
    };

    // ...
}

And this is very ergonomic for passing a HashSet but causes a problem for the None case:

// This works
contract(&x, &y, &mut out, Some(&kk));

// This fails with error: "type annotations needed, cannot infer type for type parameter `I`"
contract(&x, &y, &mut out, None); 

So you'd have to annotate None with some dummy iterator type (like Box<...> or std::iter::Empty or something) which isn't ideal.

Instead you can make keyarg the boxed iterator directly:

fn contract_1<'a, K, V, V1>(
    left: &'a MapSet<K, V>,
    right: &MapSet<V, V1>,
    out: &mut MapSet<K, V1>,
    keyarg: Option<Box<dyn Iterator<Item = &'a K> + 'a>>,
                // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
) {
    let keys: Box<dyn Iterator<Item = &K>> = match keyarg {
        Some(keys) => keys,
        None => Box::new(left.keys()),
    };

    // ...
}

Which can be used like so:

contract(&x, &y, &mut out, Some(Box::new(kk.iter())));
contract(&x, &y, &mut out, None);

Upvotes: 2

Related Questions