How to capture self consuming variable in a struct

2019-07-13 00:37发布

问题:

I have a Reader instance with a method that consumes the Reader and returns a Writer and the Writer can similarly get back to a Reader again. It's trivial to use immutably, but I can't figure out how to hide the immutability from callers and just do the reader-writer-reader dance behind the scenes with a mutable Self.

Essentially I'd like something like:

struct Container<'a> {
    reader: Reader<'a, File>
}

fn update(&mut self) {
    let writer = self.reader.as_writer();
    writer.write_something();
    self.reader = writer.as_reader();
}

which just gives cannot move out of borrowed content error. Tried to add a Box, Cell, or RefCell around the Reader which just lead to other errors.

Can the reader be hidden behind a mutable interface or does it force the whole struct hierarchy to be mutable as well? (i.e. similar to IO in Haskell)


Self contained sample with the types matching the real thing (I think)

#[derive(Debug)]
struct NoCopy(u32);

#[derive(Debug)]
struct Flipper<'a, T: 'a> {
    data: &'a mut T,
}
#[derive(Debug)]
struct Flopper<'a, T: 'a> {
    data: &'a mut T,
}
impl<'a, T> Flipper<'a, T> {
    fn flip(self) -> Flopper<'a, T> {
        Flopper{data: self.data}
    }
}
impl<'a, T> Flopper<'a, T> {
    fn flop(self) -> Flipper<'a, T> {
        Flipper{data: self.data}
    }
}

#[derive(Debug)]
struct Container<'a, T: 'a> {
    flipper: Flipper<'a, T>,
}
impl<'a, T> Container<'a, T> {
    fn run(&mut self) {
        self.flipper = self.flipper.flip().flop();
    }
}

fn main() {
    let f = Flipper{data: &mut NoCopy(42)};
    let f = f.flip().flop();
    println!("f={:?}", f);

    let mut c = Container{flipper: f};
    c.run();
    println!("c={:?}", c);
}
error[E0507]: cannot move out of borrowed content
  --> src/main.rs:29:24
   |
29 |         self.flipper = self.flipper.flip().flop();
   |                        ^^^^ cannot move out of borrowed content

回答1:

The simpler solution is to use an Option to wrap the Reader.

An Option has a take method which returns the content (and puts None in the Option), then you can place the Reader back by assigning.

struct Container<'a> {
    reader: Option<Reader<'a, File>>
}

fn update(&mut self) {
    let writer = self.reader.take().unwrap().as_writer();
                             ^~~~~~~
    writer.write_something();
    self.reader = Some(writer.as_reader());
                  ^~~~~                  ^
}


回答2:

Building on Matthieu's answer I figured it's straightforward to add a (trivial) wrapper around the Option to enforce compile time checks that only allow "borrowing" the Reader instance while requiring another one is returned afterwards.

struct State<T>(Option<T>);

impl<T> State<T> {
    pub fn new(val: T) -> Self {
        State(Some(val))
    }
    pub fn modify<F>(&mut self, fun: F)
        where F: FnOnce(T) -> T
    {
        self.0 = Some(fun(self.0.take().unwrap()));
    }
}

Usage:

fn modify(&mut self) {
    self.state.modify(|reader| {
        let writer = reader.as_writer();
        writer.write_something();
        writer.as_reader()
    }
}

Should be good enough to avoid accidental misuses. No idea about panic! unwindings though.

Probably not following Rust naming conventions, state and modify come from Haskell's State.



标签: struct rust