3

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
ljedrz
  • 20,316
  • 4
  • 69
  • 97
Tommi Komulainen
  • 2,830
  • 1
  • 19
  • 16

2 Answers2

3

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());
                  ^~~~~                  ^
}
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • I think you're missing an `.unwrap()` after the take, but with that it works. /me slaps forehead – Tommi Komulainen Oct 05 '16 at 18:55
  • Is there a way to make it a compile error if the function tries to exit without restoring a `Some` back to the reader? – Tommi Komulainen Oct 05 '16 at 19:07
  • 1
    @TommiKomulainen: Not that I know of, no. There used to be proposals for giving Rust the ability to "punch holes" in `struct`/`enum` and then restore them... but this is tricky to ensure in the presence of `panic!` and unwinding. – Matthieu M. Oct 06 '16 at 06:38
1

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.

Community
  • 1
  • 1
Tommi Komulainen
  • 2,830
  • 1
  • 19
  • 16