1

I have a struct called Cell

pub struct Cell {
    x: X, // Some other struct
    y: Y, // Some other struct
    weight: usize,
}

I was trying to select the top preference cell out of some Row (a collection of Cells).

// Return the top n-matching cells with a positive weight
pub fn select_preference(&mut self) -> Vec<Cell> {
    let top = 3;

    self.sort();
    // After sorting, omit the cells with weight = 0
    // And select the top preference cells
    self.cells.split(|cell| cell.weight() == 0).take(top)
}

However, I am getting an expected error actually:

   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
  --> src/lib.rs:35:9
   |
29 |     pub fn select_preference(&mut self) -> Vec<Cell> {
   |                                            --------- expected `Vec<Cell>` because of return type
...
35 |         self.cells.split(|cell| cell.weight() == 0).take(top)
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `Vec`, found struct `std::iter::Take`
   |
   = note: expected struct `Vec<Cell>`
              found struct `std::iter::Take<std::slice::Split<'_, Cell, [closure@src/lib.rs:35:26: 35:32]>>`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` due to previous error

I don't know how to convert the Take into Vec<Cell> or &[Cell]. I know the Take is some sort of Iterator but unable to convert it :>

Rust Playground

  • Does `collect`ing the iterator work? This can be used to place all the elements of an iterator into a container such as a vector. A minimum working example would be helpful here. – frankplow Oct 26 '22 at 15:44
  • 1
    @frankplow, the `collect` requires me to implement the `FromIterator`, which I believe isn't the idiomatic way. I added a Rust Playground link. – محمد جعفر نعمة Oct 26 '22 at 15:54

2 Answers2

2

Returning a vector of references to the relevant cells is, I think, the most idiomatic way to do this as it allows using iterators. You can then write:

pub fn select_preference(&mut self) -> Vec<&Cell> {
    let top = 3;
    self.sort();
    self.cells.iter().filter(|cell| cell.weight() != 0).take(top).collect()
}

You can even sort only the iterator and not the Vec<Cell> itself.

Returning a slice is difficult as a slice must always refer to a contiguous part of a sequence. This will always be the case here due to the sorting of cells, however the iterator methods don't take this into account and so cannot be used. One way you could do it is:

pub fn select_preference(&mut self) -> &[Cell] {
    let mut top = 3;
    self.sort();
    let mut ret = &self.cells[..cmp::min(self.cells.len(), top)];
    while ret[ret.len() - 1].weight() == 0 {
        ret = &ret[..ret.len() - 1];
    }
    ret
}

But it is probably evident that this is not very idiomatic Rust.

frankplow
  • 502
  • 1
  • 12
  • 1
    For the latter thing, you should also be able to do it with `split()`: `&self.cells.split(|cell| cell.weight() != 0).next().unwrap()[0..top]` -- but still not super advisable – Caleb Stanford Oct 26 '22 at 16:27
  • 1
    @6005 There is a potential runtime error here as there may not be `top` elements in the unwrapped slice. This is why the `cmp::min` is used in my example above. You could do this with the `split` iterator approach also, but it all gets a bit fiddly as you need the length of the iterator (which requires cloning it and giving it a name). – frankplow Oct 26 '22 at 16:32
  • Yep it's super fiddly :) – Caleb Stanford Oct 26 '22 at 16:33
1

First, split is probably not what you want -- that creates an iterator where each element is a block of nonzero items. You probably want .iter().filter(|cell| cell.weight() != 0): iterate over elements of the vector, then filter out those that are nonzero.

To return a vector from an iterator, you need .collect(). However, this would give a Vec<&Cell> -- which doesn't quite match your function signature. Since you want a Vec<Cell>, you also need to clone the elements first to get new cells -- so you can use .cloned() first. That requires adding #[derive(Clone)] to Cell. This is the end result:

#[derive(Clone)]
pub struct Cell {
    x: X,
    y: Y,
    weight: usize,
}

// Return the top n-matching cells with a positive weight
pub fn select_preference(&mut self) -> Vec<Cell> {
    let top = 3;

    self.sort();
    // After sorting, omit the cells with weight = 0
    // And select the top preference cells
    self.cells.iter().filter(|cell| cell.weight() != 0).take(top).cloned().collect()
}

As a general rule, it's common to always derive Clone for structs of data.

Other designs are possible too -- you can return the Vec<&Cell> directly, as the other answer suggests. Finally, you could return an iterator instead of a Vec; here's how that looks:

pub fn select_preference(&mut self) -> impl Iterator<Item = &Cell> {
    let top = 3;

    self.sort();
    // After sorting, omit the cells with weight = 0
    // And select the top preference cells
    self.cells.iter().filter(|cell| cell.weight() != 0).take(top)
}
Caleb Stanford
  • 453
  • 2
  • 6
  • 15
  • 1
    Another option is to use [`drain`](https://doc.rust-lang.org/1.54.0/std/vec/struct.Vec.html#method.drain) instead of `iter`, which avoids cloning but will remove the cells from `self.cells` (i.e. `self.cell` will be empty after the call): [playground](https://play.rust-lang.org/?version=stable&mode=release&edition=2021&gist=2460c1ff1711f8defe0fce887557d9e2) – Jmb Oct 27 '22 at 06:46
  • @Jmb true but probably not what the OP wants! – Caleb Stanford Oct 27 '22 at 15:30