Generics 如何在Rust中使用泛型类型的内部可变性?
我想设计一个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
摘要
特征的对象来构造,并抽象方法后面的散列行为。下面是一个不可编译的简单示例:
使用摘要::摘要;
结构加密{
文摘: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)的值。这看起来很有挑战性,因为我想支持不同的散列算法,它们会产生不同大小的散列
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()
}