Как я могу разделить изменчивый писатель между несколькими "кодировщиками"? - PullRequest
0 голосов
/ 14 апреля 2019

Я реализую интерфейс сжатия данных:

pub trait NumericEncoder<V> {
    fn encode(&mut self, value: V) -> io::Result<()>;
}

Кодер может кодировать некоторое число в каком-либо виде вывода, где выводом может быть поток (файл), байтовый буфер или даже другой кодер. Можно было бы вызвать реализацию, например, так:

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();

Это все хорошо, но в некоторых случаях мне нужно несколько кодеров для одного и того же потока вывода. Что-то вроде (упрощенно):

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);
}

Здесь два кодировщика будут чередовать свои данные во время их записи.

Это требует изменяемого доступа к тому же файлу, что невозможно при прямых ссылках &mut. Из того, что я могу сказать, единственный способ сделать это с помощью RefCell; есть ли лучший способ?

Насколько я могу судить, это сделает все реализации кодировщика менее чистыми. Прямо сейчас кодировщик может быть объявлен так:

pub struct MySpecialEncoder<'a, V, W>
where
    W: io::Write,
{
    w: &'a mut W,
    phantom: std::marker::PhantomData<V>,
}

С RefCell каждая структура и конструктор кодировщика должны будут иметь дело с Rc<RefCell<W>>, что не так хорошо и просачивает общий доступ писателя к кодировщику, который не должен знать, что писатель общий.

(я подумал, могу ли я изменить черту NumericEncoder, чтобы получить аргумент писателя, который должен быть std::io::Write. Это не сработает, потому что некоторые кодировщики не записывают в std::io::Write, но к другому NumericEncoder.)

1 Ответ

1 голос
/ 14 апреля 2019

единственный способ сделать это с помощью RefCell

Любой тип, который предоставляет изменчивость интерьера , будет работать. Например, Mutex также достаточно.

это сделает все реализации кодера менее чистыми

Не знаю, почему вы в это верите. Создайте тип, который использует внутреннюю изменчивость, и используйте этот тип только тогда, когда вам нужны дополнительные функции:

#[derive(Debug)]
struct Funnel<E>(Rc<RefCell<E>>);

impl<E> Funnel<E> {
    fn new(e: E) -> Self {
        Funnel(Rc::new(RefCell::new(e)))
    }
}

impl<E> Clone for Funnel<E> {
    fn clone(&self) -> Self {
        Funnel(self.0.clone())
    }
}

impl<V, E> NumericEncoder<V> for Funnel<E>
where
    E: NumericEncoder<V>,
{
    fn encode(&mut self, value: V) -> io::Result<()> {
        self.0.borrow_mut().encode(value)
    }
}
fn main() -> io::Result<()> {
    let s = Shared;

    let s1 = Funnel::new(s);
    let s2 = s1.clone();

    let mut e1 = Wrapper(s1);
    let mut e2 = Wrapper(s2);

    e1.encode(1)?;
    e2.encode(2)?;

    Ok(())
}

Вам также следует подумать о том, чтобы взять W по значению, и я не уверен, зачем вам нужен PhantomData - мой код не сделал.

Смотри также:

...