3
votes

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. 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. So it tries to copy it, instead, but since the D generic is not defined to adhere to the Copy trait, it fails.

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. But that moves ownership of the object into the method, after which it cannot be used again.

  • Wrapping the digest field in a RefMut or Cell, in an effort to adopt internal 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. 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. 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. 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. Pointers to idiomatic Rust solutions to this design challenge greatly appreciated.

Bonus — how do I avoid the to_vec() copy and just return the array returned by finalize_reset()?

2
chain requires you to move digest, so what do you plan on doing to replace the old digest with?Aplet123
Well I could do away with chain. But self.digest.update(&data); self.digest.finalize_reset().to_vec() still wants to borrow digest as immutable, and can't.theory
After getting rid of the chain function you can update hash's method signature to take &mut self instead of &self and that seems to meet all of your requirements, no?pretzelhammer
Ah, yes, I didn't realize chain wanted to move digest, so removing that and changing the signature to mut &self does indeed fix it, as long as I also create the Crypto object as mutable. Would be nice to keep it internal, though.theory
@theory Can you please clarify what you mean by "would be nice to keep it internal"? Is it a strong requirement that all Crypto instances remain immutable or... you want people to be able to call hash even on an immutable Crypto?pretzelhammer

2 Answers

3
votes

I'd rather not copy it, anyway. I'd rather have the one instance [of 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. 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.

Your idea to make digest a function that creates digests should work, though. 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:

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

You can have hash() return the same type as finalize(), digest::Output<D>:

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

To add to user4815162342's digest factory answer here's an alternative implementation using interior mutability:

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