【问题标题】:How do I share a mutable writer between multiple "encoders"?如何在多个“编码器”之间共享可变编写器?
【发布时间】:2019-09-04 09:33:13
【问题描述】:

我正在实现一个数据压缩接口:

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

在这里,两个编码器会在写入数据时交错数据。

这需要对同一文件进行可变访问,而直接引用 &amp;mut 是不可能的。据我所知,实现这一目标的唯一方法是使用RefCell;有没有更好的办法?

据我所知,这会使所有编码器实现变得不那么干净。现在可以像这样声明编码器:

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

使用RefCell,每个编码器结构和构造器都需要处理Rc&lt;RefCell&lt;W&gt;&gt;,这不是很好,并且会将编写器的共享性泄漏到编码器中,而编码器不需要知道编写器是共享。

(我确实考虑过是否可以更改 NumericEncoder 特征以采用 writer 参数,该参数必须是 std::io::Write。这不起作用,因为某些编码器不会写入 std::io::Write,但是到另一个NumericEncoder。)

【问题讨论】:

  • 为什么你的结构需要保存对文件的引用?当你调用 encode 时,为什么不直接给他们呢? idEnc.encode(f, id); dataEnc.encode(f, data); 这允许更大的灵活性。
  • “这不起作用,因为某些编码器不会写入 std::io::Write,而是写入另一个 NumericEncoder。”不清楚。这可能需要minimal reproducible example
  • “这行不通,因为某些编码器不写入 std::io::Write,而是写入另一个 NumericEncoder” - 那么为什么不为 T: io::Write 实现 NumericEncoder 呢?然后修改其签名以接受另一个NumericEncoder
  • Idiomatic Rust 使用snake_case 表示变量、方法、宏、字段和模块; UpperCamelCase 用于类型和枚举变体; SCREAMING_SNAKE_CASE 用于静态和常量。请改用id_enc / data_enc
  • 这些问题让我意识到我没有考虑过签名。即使某些编码器写入另一个编码器,而不是W,我当然可以将W 作为签名的一部分(encode(W, V)),因为编码器可以只将 writer 参数传递给它的下一个编码器,而不是使用它.这意味着编码器结构不需要携带编写器。谢谢@Laney 和@Stargateur。

标签: rust mutability interior-mutability


【解决方案1】:

完成此操作的唯一方法是使用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 — 我的代码没有。

另见:

【讨论】:

  • 谢谢,这给了我一些值得阅读的东西! PhantomData 在那种情况下不是很有用,我猜,但我一直在其他地方使用它来避免返回类型类型推断:我可以创建一个具体的 SomeDecoder&lt;u32&gt; var 并且知道 let v = decoder.decode() 总是返回 @987654334 @,而不是 let v: io::Result&lt;u32&gt; = decoder.decode()。似乎在let v: u32 = decoder.decode()? 中,Rust 无法推断出Result 的内部类型变量,但我可能是错的。我收到一个编译错误,说我的函数返回 ()。与match decoder.decode() 中的类型推断相同。
猜你喜欢
  • 2019-07-03
  • 2020-09-08
  • 2011-08-12
  • 2019-10-02
  • 1970-01-01
  • 2018-01-22
  • 2013-06-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多