我想在Rust中设计一个结构,该结构可以用实现Digest
特性的对象来构造,并抽象方法背后的哈希行为。下面是一个不编译的简单示例:
use digest::Digest;
struct Crypto<D: Digest> {
digest: D,
}
impl<D> Crypto<D>
where
D: Digest,
{
pub fn hash(&self, data: &[u8]) -> Vec<u8> {
self.digest.chain(&data).finalize_reset().to_vec()
}
}
这无法编译,因为self
在方法签名中是免疫借用的,因此self.digest
不能免疫借用。因此,它尝试复制它,但由于D
泛型没有被定义为遵守Copy
特性,因此它失败了。
不管怎样,我宁愿不复制它。我宁愿举一个例子。我尝试过的一些东西:
更改方法签名以采用
mut self
。但这会将对象的所有权转移到方法中,之后就不能再使用了。将
digest
字段封装在RefMut
或Cell
中,试图采用内部可变性,但我无法找到正确的方法,在不尝试复制值的情况下可变地借用digest
。此外,如果可能的话,我更愿意在编译时保留借用支票。将
D
的类型更改为返回Digest
实例的函数,并使用它在hash()
方法内实例化新摘要。但是,即使我将其定义为D: Box<dyn Digest>
,编译器也会抱怨the value of the associated type OutputSize (from trait digest::Digest) must be specified
。这似乎很有挑战性,因为我想支持不同的哈希算法,这些算法可以产生不同大小的哈希。
我曾试图使用泛型来获得特征边界在编译时的好处,但必须承认,在与行为需要可变性的对象进行组合时,内部可变性的挑战正在阻碍我。非常感谢使用惯用Rust解决方案来解决这一设计挑战的人。
额外的好处——如何避免to_vec()
副本,只返回finalize_reset()
返回的数组?
无论如何,我宁愿不复制它。我宁愿拥有[
self.digest
]的一个实例。
问题是self.digest.chain()
消耗(拥有)self.digest
,这是Digest::chain()
合同的基本部分,您不能更改。内部可变性没有帮助,因为这不是可变性问题,而是对象生存期问题——在移动或丢弃对象后,不能使用对象。
不过,让digest
成为一个创建摘要的函数的想法应该是可行的。它将需要两个泛型类型,一个用于摘要类型,其特征界为Digest
,另一个用于工厂,其特征边界为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()
}
}
如何避免
to_vec()
副本,只返回finalize_reset()
返回的数组?
您可以让hash()
返回与finalize()
相同的类型,digest::Output<D>
:
pub fn hash(&self, data: &[u8]) -> digest::Output<D> {
(self.digest_factory)()
.chain(&data)
.finalize()
}
要添加到user4815162342的摘要工厂答案中,这里有一个使用内部可变性的替代实现:
use digest::Digest;
use std::cell::RefCell;
struct Crypto<D: Digest> {
digest: RefCell<D>,
}
impl<D> Crypto<D>
where
D: Digest,
{
pub fn hash(&self, data: &[u8]) -> Vec<u8> {
let mut digest = self.digest.borrow_mut();
digest.update(&data);
digest.finalize_reset().to_vec()
}
}
操场