Generics 如何在Rust中使用泛型类型的内部可变性?

Generics 如何在Rust中使用泛型类型的内部可变性?,generics,rust,traits,composition,interior-mutability,Generics,Rust,Traits,Composition,Interior Mutability,我想设计一个Rust结构,它可以用一个实现摘要特征的对象来构造,并抽象方法后面的散列行为。下面是一个不可编译的简单示例: 使用摘要::摘要; 结构加密{ 文摘:D, } impl加密 哪里 D:文摘, { 发布fn散列(&self,数据:&u8])->Vec{ self.digest.chain(&data).finalize_reset().to_vec() } } 这无法编译,因为在方法签名中,self是不可变借用的,因此,self.digest不能不可变借用。因此它尝试复制它,但是由于D

我想设计一个Rust结构,它可以用一个实现
摘要
特征的对象来构造,并抽象方法后面的散列行为。下面是一个不可编译的简单示例:

使用摘要::摘要;
结构加密{
文摘:D,
}
impl加密
哪里
D:文摘,
{
发布fn散列(&self,数据:&u8])->Vec{
self.digest.chain(&data).finalize_reset().to_vec()
}
}
这无法编译,因为在方法签名中,
self
是不可变借用的,因此,
self.digest
不能不可变借用。因此它尝试复制它,但是由于
D
泛型没有定义为遵循
copy
特征,因此它失败了

无论如何,我不想复制它。我宁愿只举一个例子。我尝试过的一些事情:

  • 将方法签名改为采用
    mut self
    。但这会将对象的所有权转移到方法中,之后就不能再使用它了

  • 摘要
    字段包装在
    RefMut
    单元格
    中,以尝试采用,但我无法找到正确的方法,然后在不复制值的情况下可变地借用
    摘要
    。此外,如果可能的话,我们更愿意在编译时保留借阅检查

  • D
    的类型更改为返回
    摘要的实例的函数,并使用它在
    hash()
    方法内实例化新摘要。但是,即使我将其定义为
    D:Box
    ,编译器也会抱怨
    必须指定关联类型OutputSize(来自trait digest::digest)的值。这看起来很有挑战性,因为我想支持不同的散列算法,它们会产生不同大小的散列

我试图使用泛型来获得trait边界在编译时的好处,但不得不承认,当与行为需要可变性的对象组合时,内部可变性的挑战阻碍了我。我们非常欣赏针对这一设计挑战的惯用防锈解决方案

奖金-如何避免复制并返回

无论如何,我不想复制它。我想要一个[代码>自我摘要]的例子

问题是
self.digest.chain()
消耗(拥有)
self.digest
,这是
digest::chain()
契约的基本部分,您无法更改。内部可变性没有帮助,因为它不是一个可变性问题,而是一个对象生命周期问题——在移动或删除对象后,您不能使用它

不过,让
摘要
成为创建摘要的函数的想法应该是可行的。它需要两个泛型类型,一个用于摘要类型,其特征范围为
摘要
,另一个用于工厂,其特征范围为
Fn()->D

struct Crypto<F> {
    digest_factory: F,
}

impl<D, F> Crypto<F>
where
    D: Digest,
    F: Fn() -> D,
{
    pub fn hash(&self, data: &[u8]) -> Vec<u8> {
        (self.digest_factory)()
            .chain(&data)
            .finalize()  // use finalize as the object is not reused
            .to_vec()
    }
}
要添加到以下内容,请使用内部可变性实现替代方案:

使用摘要::摘要;
使用std::cell::RefCell;
结构加密{
文摘:RefCell,
}
impl加密
哪里
D:文摘,
{
发布fn散列(&self,数据:&u8])->Vec{
让mut digest=self.digest.borrow_mut();
摘要。更新(和数据);
摘要.将_reset()最终确定为_vec()
}
}

chain
要求您移动
digest
,那么您打算如何使用
chain
替换旧的
digest
。但是
self.digest.update(&data);self.digest.finalize_reset().to_vec()
仍然想借用
digest
作为不可变的,并且不能。在去掉
函数后,您可以更新
散列
的方法签名,以采用
&mut self
而不是
&self
,这似乎满足了您的所有要求,不是吗?啊,是的,我没有意识到
chain
想要移动
digest
,所以删除它并将签名更改为
mut&self
确实可以修复它,只要我还将加密对象创建为可变的。不过,最好把它放在内部。@你能澄清一下你所说的“最好把它放在内部”是什么意思吗?是否强烈要求所有
加密
实例保持不变或。。。您希望人们能够调用
哈希
,即使是在不可变的
加密
?完美,几乎正是我所需要的。如果能够存储一个摘要对象以供重用就好了,但是我可以为每个调用构造一个新的摘要对象。我将用它来换取不将摘要输出复制到向量。谢谢可以调用
finalize()
而不是
finalize\u reset()
,因为该对象从未被重用。@很好,我现在修改了答案。我从未使用过
digest::digest
,所以我只使用了问题中的代码。啊,需要同时使用RefCell和Rc,这是我缺少的一点。谢谢不过,我想我更喜欢工厂的整体整洁功能。很高兴把它放在我的口袋里,以备将来使用。很好-如果你能摆脱
链,内部的可变性确实是可能的,我没有意识到这是可选的。但是为什么
摘要
Rc
?如果没有额外的堆分配,
RefCell
会不会工作得很好?只要尝试一下,@user4815162342,使用
RefCell
@theory它似乎确实工作得很好。它可能在原始代码中不工作,因为您仍在调用
chain()
它想使用它-但这对
Rc
也不起作用。感谢您的评论,我已将我的答案更新为仅使用
RefCell
RefCell
Rc
之间的关键区别只有在为
Crypto
实现
Clone
时才会发挥作用。在t
pub fn hash(&self, data: &[u8]) -> digest::Output<D> {
    (self.digest_factory)()
        .chain(&data)
        .finalize()
}