I'm implementing a data compression interface:
pub trait NumericEncoder<V> {
fn encode(&mut self, value: V) -> io::Result<()>;
}
An encoder can encode some number in some kind of output, where an output might be a stream (file), byte buffer or even another encoder. One might would invoke an implementation like so:
let f = File::create("out").unwrap();
// Delta encoder whose data is run-length-compressed
let mut enc = DeltaEncoder::new(RunLengthEncoder::new(f));
enc.encode(123).unwrap();
That's all fine and good, but in some cases I need multiple encoders against the same output stream. Something like (simplified):
let f = File::create("out")?;
let mut idEnc = RunLengthEncoder::new(DeltaEncoder::new(f));
let mut dataEnc = LZEncoder::new(f);
for (id, data) in input.iter() {
idEnc.encode(id);
dataEnc.encode(data);
}
Here, two encoders would be interleaving their data as they're writing it.
This needs mutable access to the same file, which isn't possible with straight &mut
references. From what I can tell, the only way to accomplish this is with a RefCell
; is there a better way?
As far as I can tell, this would make all the encoder implementations less clean. Right now an encoder can be declared like this:
pub struct MySpecialEncoder<'a, V, W>
where
W: io::Write,
{
w: &'a mut W,
phantom: std::marker::PhantomData<V>,
}
With a RefCell
, every encoder struct and constructor would need to deal with Rc<RefCell<W>>
, which is not as nice and leaks the sharedness of the writer into the encoder, which shouldn't need to know that the writer is shared.
(I did consider whether I could change the NumericEncoder
trait to take a writer argument, which would have to be std::io::Write
. This won't work because some encoders don't write to a std::io::Write
, but to another NumericEncoder
.)