Cloned can't be used on tuples?

Hi, I'm new to Rust and still try to how ownership and lifetime work.

I have a simple function that filters a ML model output, a vector of probabilities, with a threshold and returns the values and their indices in the output vector.

fn filterOutput(output: Vec<f32>, threshold: f32) -> Vec<(f32, usize)>{
        let size = output.len();
        let indices: Vec<usize> = (0..size).collect();
        let outputWithIndices: Vec<(&f32, &usize)> = output.iter().zip(indices.iter()).filter(|(&p,&n)| p > threshold).collect();
        let r: Vec<(f32, usize)> = outputWithIndices.cloned().collect();
        r
    }

outputWithIndices.cloned(). doesn't work.
Are there more efficient ways to return the filtered result without unnecessary copies of data?

Note that (&f32, &usize) is larger than (f32, usize), so you're not really saving any work here. (It might be the same size, actually, due to padding)

ok. I was simply trying to zip the two vectors together since I didn't find another way.
Intuitively, there should be a method to zip them together whiling cloning from both.

fn filterOutput(output: Vec<f32>, threshold: f32) -> Vec<(f32, usize)>{
        output.iter().copied().zip(0..output.len()).collect()
    }
1 Like
    fn filterOutput(output: Vec<f32>, threshold: f32) -> Vec<(f32, usize)>{
        let size = output.len();
        let indices: Vec<usize> = (0..size).collect();
-       let outputWithIndices: Vec<(&f32, &usize)> = output.iter().zip(indices.iter()).filter(|(&p,&n)| p > threshold).collect();
-       let r: Vec<(f32, usize)> = outputWithIndices.cloned().collect();
+       let r: Vec<(f32, usize)>                   = output.iter().zip(indices.iter()).filter(|(&p,&n)| p > threshold).cloned().collect();
        r
    }

EDIT: @Hyeonu beat me to it :upside_down_face:

Thanks!
I got the final solution

output.iter().copied().zip(0..output.len()).filter(|(p,n)| p > &threshold).collect()

|(&p,&n)| p > threshold doesn't compile. I can understand threshold is used many times in the filter and it should be &threshold.
I think for similar reasons, p and n need to have & as well because the values need to be returned.
but it is not the case. why (p,n) works while (&p,&n) doesn't?

With the latest impl Iterator you wrote, the elements yielded by the Iterator are now (f32, usize) owned pairs.

You then apply the .filter() adaptor, which takes a &Item as input, i.e.:
a &(f32, usize), which is not the same as a (&f32, &usize).

So, once you receive a parameter of type &(f32, usize), in classical Rust, there would be mainly two kind of patterns able to deconstruct that:

  • either pair (so that pair: &(f32, usize)) or &pair (so that pair: (f32, usize));

  • or &(p, n) (so that p: f32, and n: usize).

As you can see, (p, n) is not part of that list.

It turns out that nowadays, thanks to a "feature" called match ergonomics, you can pattern-match a (p, n) to an input &(_, _), and in that case so doing will be sugar for:
&(ref p, ref n), so that p: &f32, and n: &usize.

Basically, thanks to because of that (bitter) sugar, the & kind of magically commutes with the tuple, so that things work kind of intuitive-ish-ly.

  • (As you can see, this is not always helpful, especially when the types are not well-known, since that flexibility obfuscates the readability of types and patterns).

Back to your question, in order to get an p: f32 and n: usize, you must use the pattern I showcased in that second bullet:

output                      // : &'_ Vec<f32>
    .iter()                 // : impl Iterator<Item = &f32> + '_
    .copied()               // : impl Iterator<Item = f32> + '_
    .zip(0 .. output.len()) // : impl Iterator<Item = (f32, usize)> + '_
    .filter(|&(p, n): &(f32, usize)| p > threshold) // ditto
    .collect()              // : Vec<(f32, usize)>
1 Like

@Yandros thank your for the detailed explanation! much appreciated!