简体   繁体   English

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

[英]How can I use internal mutability with generic type in Rust?

I would like to design a struct in Rust that can be constructed with an object that implements the Digest trait, and abstract the behavior of the hash behind a method.我想在 Rust 中设计一个结构,它可以用实现Digest特征的 object 构造,并在方法后面抽象 hash 的行为。 Here's a simple example that doesn't compile:这是一个无法编译的简单示例:

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

This fails to compile because self is immutably borrowed in the method signature, so self.digest cannot be immutably borrowed.这无法编译,因为self在方法签名中被不可变地借用,因此self.digest地借用。 So it tries to copy it, instead, but since the D generic is not defined to adhere to the Copy trait, it fails.所以它尝试复制它,但是由于D泛型没有被定义为遵循Copy特征,所以它失败了。

I'd rather not copy it, anyway.无论如何,我宁愿不复制它。 I'd rather have the one instance.我宁愿有一个实例。 Some things I've tried:我尝试过的一些事情:

  • Changing the method signature to take mut self instead.将方法签名更改为采用mut self But that moves ownership of the object into the method, after which it cannot be used again.但这会将 object 的所有权转移到方法中,之后就无法再次使用。

  • Wrapping the digest field in a RefMut or Cell , in an effort to adoptinternal mutability , but I was not able to figure out the right method to then borrow the digest mutably without it trying to copy the value.digest字段包装在RefMutCell中,以努力采用内部可变性,但我无法找到正确的方法,然后在不尝试复制值的情况下可变地借用digest Also, would prefer to keep borrow checks at compile-time if possible.此外,如果可能,希望在编译时保留借用检查。

  • Change the type of D to a function that returns an instance of a Digest , and use it to instantiate a new digest inside the hash() method.D的类型更改为返回Digest实例的 function ,并使用它在hash()方法中实例化新的摘要。 But then, even if I define it as D: Box<dyn Digest> , the compiler complains that the value of the associated type OutputSize (from trait digest::Digest) must be specified .但是,即使我将其定义为D: Box<dyn Digest> ,编译器也会抱怨the value of the associated type OutputSize (from trait digest::Digest) must be specified So that seems challenging, since I want to support different hashing algorithms that will produce hashes of varying sizes.所以这看起来很有挑战性,因为我想支持不同的哈希算法,这些算法会产生不同大小的哈希值。

I was trying to use generics to get the compile-time benefits of trait bounds, but have to admit that the challenges of internal mutability when composing with objects whose behavior require mutability is thwarting me.我试图使用 generics 来获得 trait bound 的编译时优势,但不得不承认,在与行为需要可变性的对象组合时,内部可变性的挑战阻碍了我。 Pointers to idiomatic Rust solutions to this design challenge greatly appreciated.非常感谢针对此设计挑战的惯用 Rust 解决方案的指针。

Bonus — how do I avoid the to_vec() copy and just return the array returned by finalize_reset() ?奖励 - 我如何避免to_vec()复制并只返回finalize_reset()返回的数组

I'd rather not copy it, anyway.无论如何,我宁愿不复制它。 I'd rather have the one instance [of self.digest ].我宁愿有一个 [ self.digest ] 的实例。

The problem is that self.digest.chain() consumes (takes ownership of) self.digest , and that's a fundamental part of the contract of Digest::chain() which you cannot change.问题是self.digest.chain()消耗(拥有) self.digest ,这是您无法更改的Digest::chain()合同的基本部分。 Interior mutability won't help because it's not a mutability issue, it's an object lifetime issue - you cannot use an object after it is moved or dropped.内部可变性无济于事,因为它不是可变性问题,而是 object 生命周期问题 - 在移动或丢弃 object 后,您不能使用它。

Your idea to make digest a function that creates digests should work, though.不过,您创建digest的 function 的想法应该可行。 It will require two generic types, one for the digest type, with a trait bound of Digest , and the other for the factory, with a trait bound of Fn() -> D :它将需要两种泛型类型,一种用于摘要类型,具有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()
    }
}

how do I avoid the to_vec() copy and just return the array returned by finalize_reset() ?如何避免to_vec()复制并仅返回finalize_reset()返回的数组?

You can have hash() return the same type as finalize() , digest::Output<D> :您可以让hash()返回与finalize()相同的类型, digest::Output<D>

pub fn hash(&self, data: &[u8]) -> digest::Output<D> {
    (self.digest_factory)()
        .chain(&data)
        .finalize()
}

To add to user4815162342's digest factory answer here's an alternative implementation using interior mutability:要添加到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()
    }
}

playground 操场

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM