1

I'm trying to create a function that takes a &[Vec<u64>] and returns vector of tuple "coordinates" for the largest i64 in each row using iterators. I have it working on concrete types, but I want it to be generic over T and bounded to iterable types. My code so far:

fn find_largest_per_row<T>(input: &[T]) -> Vec<(usize, usize)> 
where
    T: IntoIterator<Item = u64>,
{
    input
        .iter()
        .enumerate()
        .map(|(r, v)| {
            v.into_iter()
                .enumerate()
                .max_by_key(|&(_, v)| v)
                .map(|(c, _)| (r, c))
                .unwrap()
        })
        .collect::<Vec<_>>()
}

I'm getting:

cannot move out of `*v` which is behind a shared reference

How do I resolve this? I realize T is a reference so I tried .cloned(), but that didn't work.

Also, for IntoIterator<Item=u64>, do I need to specify u64 or can I provide something more generic?

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
marcantonio
  • 958
  • 10
  • 24
  • 1
    You can' have `T: IntoIterator`, because `T` is in a `&[T]`, so it's always behind a shared reference where `IntoIterator` can't take `self` to move the receiver into the iterator. – user2722968 Jul 29 '21 at 21:06

1 Answers1

5

IntoIterator::into_iter takes self, which means it consumes (or moves) the object.

You have added the bound T: IntoIterator so, since that's the only IntoIterator around, the compiler will use T's implementation. Calling into_iter on T will always move the value, which is not ok because the function has only been given a reference to it and T might not be Copy - in fact, for Vec it definitely isn't.

But IntoIterator is also implemented for most references to iterable types, for example &Vec<T>, where the item is a reference too. These are ok to move because references are Copy, so the original data is left intact when they are moved.

You can change your function signature like this:

fn find_largest_per_row<'a, T>(input: &'a [T]) -> Vec<(usize, usize)>
where
    &'a T: IntoIterator<Item = &'a u64>,
{
    input
        .iter()
        .enumerate()
        .map(|(r, v)| {
            v.into_iter()
                .enumerate()
                .max_by_key(|&(_, v)| v)
                .map(|(c, _)| (r, c))
                .unwrap()
        })
        .collect::<Vec<_>>()
}

To answer the second question, yes, you can make the item generic. Instead of specifying a concrete type, you can omit it and then specify the bounds that the code requires. You are moving it by value, so it needs to be Copy (or else you need to change the code to clone it and make it Clone instead), and you are using max_by_key, which requires it to be Ord. The function signature would then be:

fn find_largest_per_row<'a, T>(input: &'a [T]) -> Vec<(usize, usize)>
where
    &'a T: IntoIterator,
    <&'a T as IntoIterator>::Item: Ord + Copy,

See also:

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
  • Thanks, this was a great answer! I've never had more trouble learning a language than Rust. Am I missing some fountain of knowledge or is this just a lot of trail and error? – marcantonio Jul 29 '21 at 23:39
  • 1
    @marcantonio There is definitely a lot to learn. Like anything else, it starts to come easier with practice, but Rust's learning curve is steep - especially at the start. – Peter Hall Jul 29 '21 at 23:43