I’m trying to figure out the best way to implement the code below. The Source
and Sink
traits are something like a database. The Source
allows data to be read from it, and the Sink
allows data to be written. A group of writes needs to be finished with a flush
in order for the changes to be committed (it’s like a transaction in a database).
The first
module is the code I have now. There is an outer type that has some operations. It makes use of an inner type that assists with the write. The write operation will need to be done in an interleaved manner between them (hence complicating the mutable borrow). This first
module solves this by not including the Sink
in the type, and instead passing it around as necessary. This works, and in some ways maybe the most simple. But, it does make for a lot of data to pass around.
The second
module (which I haven’t been able to get to work), was my attempt to put the Sink
inside of the type, using Rc
and RefCell
to allow inner mutability. I’ve not been able to come up with a type for into_inner()
that will describe the lifetimes right.
My main question: what would be the recommended way of implementing this? In the real application, these types will have many more methods, and especially Outer
will have other Inner
types of types that will also have methods that need to be interleaved.
The other way I can think of doing this would be to get rid of the mut on add
within Sink
. I would have to wrap the struct around another type using Rc
or Arc
in order to be able to share this item.
Thanks,
David
// Mutability exercise.
#![allow(dead_code)]
fn main() {
let mut aa = MySource::new();
{
let mut aaw = aa.get_sink().unwrap();
let ff = first::Outer::new();
ff.frob(&mut *aaw);
aaw.flush();
}
println!("aa: {:?}", aa.all());
let mut bb = MySource::new();
{
let mut bbw = bb.get_sink().unwrap();
let ss = second::Outer::new(bbw);
ss.into_inner().flush();
}
println!("bb: {:?}", bb.all());
}
// A source is initially opened, the type being determined by what is actually
// found on the device.
pub trait Source {
fn get_sink<'a>(&'a mut self) -> Option<Box<Sink + 'a>>;
fn all(&self) -> Vec<u32>;
}
// To update, request a sink, `add` with it, and then flush.
pub trait Sink {
fn add(&mut self, item: u32);
fn flush(self: Box<Self>);
}
// A very simple implementation of this.
struct MySource {
items: Vec<u32>,
}
impl MySource {
fn new() -> MySource {
MySource {
items: Vec::new(),
}
}
}
impl Source for MySource {
fn all(&self) -> Vec<u32> {
self.items.iter().map(|&x| x).collect()
}
fn get_sink<'a>(&'a mut self) -> Option<Box<Sink + 'a>> {
Some(Box::new(MySink {
parent: self,
}))
}
}
struct MySink<'a> {
parent: &'a mut MySource,
}
impl<'a> Sink for MySink<'a> {
fn add(&mut self, item: u32) {
self.parent.items.push(item);
}
fn flush(self: Box<Self>) {
// Nothing to do in the example.
}
}
//----------------------------------------------------------------------
// For example.
// In this first instance, the two structs don't contain the Sink and it gets
// passed around as needed. This works, but the Sink has to be passed all
// around to make things work.
mod first {
use super::*;
pub struct Outer {
other: u32,
}
impl Outer {
pub fn new() -> Outer {
Outer {
other: 1,
}
}
pub fn frob(&self, sink: &mut Sink) {
let inside = Inner::new();
sink.add(self.other);
inside.frob(sink, self.other + 1);
sink.add(self.other + 2);
}
}
struct Inner {
other: u32,
}
impl Inner {
fn new() -> Inner {
Inner {
other: 100,
}
}
fn frob(&self, sink: &mut Sink, extra: u32) {
sink.add(self.other + extra);
}
}
}
// For this second instance, we wrap up the Sink with inner mutability so that
// it can just be stored directly in the instances.
mod second {
use super::*;
use std::cell::RefCell;
use std::rc::Rc;
pub struct Outer<'a> {
other: u32,
sink: Rc<RefCell<Box<Sink + 'a>>>,
}
impl<'a> Outer<'a> {
pub fn new<'b>(sink: Box<Sink + 'b>) -> Outer<'b> {
Outer {
other: 1,
sink: Rc::new(RefCell::new(sink)),
}
}
pub fn into_inner(self) -> Box<Sink + 'a> {
// cannot move out of borrowed content
self.sink.into_inner()
}
}
}