Reputation: 1276
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
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