I'm implementing a Merkle Tree based hash function, which uses an existing hash function as its base, and I ran into some annoying aspects of the Digest trait.
The documentation describes Digest as a "wrapper" around various other traits, namely Update, FixedOutput, Reset, Clone, and Default. But it is not actually a subtrait of these traits! This was very surprising to me.
In fact, it duplicates some functionality of the other traits, without being an implementation of them. In particular Digest::chain is an exact duplicate of Update::chain, Digest::reset is an exact duplicate of Reset::reset, Digest::update duplicates Update::update and the associated type Digest::OutputSize duplicates FixedOutput::OutputSize.
I ran into this when I tried to use finalize_into on a generic bounded by Digest. This function (which is defined in FixedOutput) is not available on Digest because Digest does not actually implement FixedOutput, it just requires it internally and then duplicates some of its functionality in its API (but not finalize_into).
So, my next idea was to set the generic bound to Digest + Update + FixedOutput + Reset. This causes chaos though, because now there are conflicts for the names chain, reset, and OutputSize, so you have to explicitly tell Rust whether you want the one defined by Digest or the one from the underlying trait, even though they should be the same. There was also some trouble because Rust could not see that FixedOutput::OutputSize and Digest::OutputSize are equal, and I had some variables that had to match both.
Finally, I decided Digest was not usable and I decided to simply use the underlying traits. This fixed all my problems but it's a shame to do without the convenience functions.
I don't really understand why the Digest trait works this way.
Concretely, I would suggest making Digest an actual subtrait of the traits it wraps, and removing the confusing duplicated functionality. This would technically be a breaking change. I would also move the digest::Output type alias to the fixed module.
(The DynDigest trait is very similar, everything I've said applies to that as well)
I'm implementing a Merkle Tree based hash function, which uses an existing hash function as its base, and I ran into some annoying aspects of the
Digesttrait.The documentation describes
Digestas a "wrapper" around various other traits, namelyUpdate,FixedOutput,Reset,Clone, andDefault. But it is not actually a subtrait of these traits! This was very surprising to me.In fact, it duplicates some functionality of the other traits, without being an implementation of them. In particular
Digest::chainis an exact duplicate ofUpdate::chain,Digest::resetis an exact duplicate ofReset::reset,Digest::updateduplicatesUpdate::updateand the associated typeDigest::OutputSizeduplicatesFixedOutput::OutputSize.I ran into this when I tried to use
finalize_intoon a generic bounded byDigest. This function (which is defined inFixedOutput) is not available onDigestbecauseDigestdoes not actually implementFixedOutput, it just requires it internally and then duplicates some of its functionality in its API (but notfinalize_into).So, my next idea was to set the generic bound to
Digest + Update + FixedOutput + Reset. This causes chaos though, because now there are conflicts for the nameschain,reset, andOutputSize, so you have to explicitly tell Rust whether you want the one defined byDigestor the one from the underlying trait, even though they should be the same. There was also some trouble because Rust could not see thatFixedOutput::OutputSizeandDigest::OutputSizeare equal, and I had some variables that had to match both.Finally, I decided
Digestwas not usable and I decided to simply use the underlying traits. This fixed all my problems but it's a shame to do without the convenience functions.I don't really understand why the
Digesttrait works this way.Concretely, I would suggest making
Digestan actual subtrait of the traits it wraps, and removing the confusing duplicated functionality. This would technically be a breaking change. I would also move thedigest::Outputtype alias to thefixedmodule.(The
DynDigesttrait is very similar, everything I've said applies to that as well)