0

I'm trying to create code that reads data from some source in both directions without code duplication.

use std::iter;

struct Reader {
    data: Vec<usize>,
    data_rev: Vec<usize>,
    data_it: Box<dyn Iterator<Item = usize>>,
    data_it_rev: Box<dyn Iterator<Item = usize>>,
}

enum Direction { Forward, Backward }

impl Reader {

    fn read(&mut self, n: usize, direction: Direction) {
        let (it, adder) = match direction {
            Direction::Forward => {
                let adder: Box<dyn FnMut(usize) -> ()> = Box::new(|idx| self.data.push(idx));
                (&self.data_it, adder)
            }
            Direction::Backward => {
                let adder: Box<dyn FnMut(usize) -> ()> = Box::new(|idx| self.data_rev.insert(0, idx));
                (&self.data_it_rev, adder)
            }
        };
        for idx in it.by_ref().take(n) {
            adder(idx);
        }
    }
}

Full code is here

What I'm trying to represent in this example is read function that has common code and some code that varies with the direction in which read should happen. It's obvious how to write this with multiple pattern matches on direction, but I want to do this without repeating myself.

If possible I'd like to make direction some kind of generic parameter and have something like

struct Reader<D: Directon> {
    data: Vec<usize>,
    data_it: Box<Iterator<Item = usize>>
}
trait Reader<Direction> {
    fn read(&mut self)
}
// somewhere in code
let reader = match direction {
    Direction::Forward => self.forward_reader;
    Direction::Backward => self.backward_reader;
}
reader.read()
user1685095
  • 5,787
  • 9
  • 51
  • 100
  • 1
    I think this question is answered by [Mutably borrow one struct field while borrowing another in a closure](https://stackoverflow.com/questions/36379242/mutably-borrow-one-struct-field-while-borrowing-another-in-a-closure). When you use `self.data` in a closure, the closure borrows all of `self`; you can circumvent it by creating a variable that just borrows `self.data` and using that inside the closure. There are some other errors your code raises but they are fairly simple mutability issues. – trent Mar 22 '20 at 22:17
  • 1
    [Here's the answer to the other question applied to your problem, plus other necessary fixes to make it compile.](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=dba02527cd9a49add3f32a8c29d24f0f) I'm not quite sure where you're going with the generic version, so I can't comment on that part. – trent Mar 22 '20 at 22:22

1 Answers1

1

Note that putting the direction in a generic parameter as in your final code means that the direction is statically determined for each instance of Reader and cannot be changed. If this is what you want, you can do it by having Direction be a trait and Forward and Backward be types that implement that trait:

trait Direction{}
struct Forward{}
impl Direction for Forward {}
struct Backward{}
impl Direction for Backward {}

struct Reader<D: Direction> {
    data: Vec<usize>,
    data_it: Box<dyn Iterator<Item = usize>>,
    direction: D,
}

impl Reader<Forward> {
    fn read (&mut self) { unimplemented!(); }
}

impl Reader<Backward> {
    fn read (&mut self) { unimplemented!(); }
}

If your Reader struct must also include some common functions that do not depend on the direction, you can add those functions in an extra generic impl block:

impl<T: Direction> Reader<T> {
    fn common_function (&self) { unimplemented!(); }
}

Since you want to be able to have instances of each and switch between them at runtime, you will need to define the interface in a trait, then use references to this trait. Moreover in order to be able to access the common functions from a trait implementation, they will also need to be defined in a super trait:

pub trait Direction{}
pub struct Forward{}
impl Direction for Forward {}
pub struct Backward{}
impl Direction for Backward {}

pub trait CommonReader {
    fn common_function (&self);
}

pub trait ReaderItf: CommonReader {
    fn read (&mut self);
}

pub struct Reader<D: Direction> {
    pub data: Vec<usize>,
    pub data_it: Box<dyn Iterator<Item = usize>>,
    pub direction: D,
}

impl ReaderItf for Reader<Forward> {
    fn read (&mut self) { self.common_function(); }
}

impl ReaderItf for Reader<Backward> {
    fn read (&mut self) { unimplemented!(); }
}

impl<T: Direction> CommonReader for Reader<T> {
    fn common_function (&self) { unimplemented!(); }
}

// And you use it like this wherever you want to switch at runtime:
pub fn use_reader (r: &mut dyn ReaderItf) {
    r.read();
}

Playground

Jmb
  • 18,893
  • 2
  • 28
  • 55
  • @trentcl because that's what the compiler suggests if you omit the `direction` field. But you're right it could be directly of type `D`, I'll change it presently. – Jmb Mar 23 '20 at 12:23
  • There should always be two readers - one forward one another backward one for me, so they statically known. What I'm not sure about is how to change them at runtime? By using a Boxed reference? – user1685095 Mar 23 '20 at 17:13
  • when trying to use common function from read I get error `no method found common_function` – user1685095 Mar 23 '20 at 21:03
  • Accessing `common_function` from `read` works in the [playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=ac6bfc7ff291028678fa866d22deb8fb). – Jmb Mar 24 '20 at 07:23
  • Sorry, my bad, I meant accessing read from common function doesn't work. https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=fbaab5eebde2a3de5ff0e0e7a10c4c5b – user1685095 Mar 24 '20 at 07:45
  • 1
    @user1685095 If you push the different behavior into the `impl` blocks, you can unsize the type parameter and work with `Reader`. [Here's an example](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=cc72bf05cf7d4d4c6d7b30d3d4a03dd3); does that help you? – trent Mar 24 '20 at 13:48
  • It kind of does, but I don't think I like this solution. The point was to kind of use template method pattern only with generics for efficiency reasons. This solution isn't any more efficient than classic template method. But thanks anyway. If you can point to resources that could explain how to do generic template method that would be great. – user1685095 Mar 25 '20 at 17:14
  • @user1685095 Sorry, I didn't see this until today (only the author of the post you comment on is notified automatically). Like I mentioned in the question comments, I don't quite understand what you're going for, but you can certainly [add other "common" generic methods](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=1313ff0dac1b540385e02b054ba6453c) to `Reader`. My earlier example was focusing more on how they would be dispatched when you don't know the direction until runtime. – trent Apr 01 '20 at 14:48