I have a little bit of trouble wrapping my head around this problem. I am trying to write a generic function which can take any digest::Digest
and spits out a string form of the computed digest ("hex string").
Here is the non-generic version as minimal example:
#![forbid(unsafe_code)]
#![forbid(warnings)]
extern crate sha2; // 0.9.1
use sha2::{Sha256, Digest}; // 0.9.1
fn main() {
let hash = Sha256::new().chain("String data").finalize();
let s = format!("{:x}", hash);
println!("Result: {}", s);
}
... and here is my attempt at a generic version:
#![forbid(unsafe_code)]
#![forbid(warnings)]
extern crate sha2; // 0.9.1
extern crate digest; // 0.9.0
use digest::Digest;
use sha2::Sha256;
fn compute_hash<D: Digest>(input_data: &str) -> String {
let mut hasher = D::new();
hasher.update(input_data.as_bytes());
let digest = hasher.finalize();
format!("{:x}", digest)
}
fn main() {
let s = compute_hash::<Sha256>("String data");
println!("Result: {}", s);
}
... which gives the following error:
Compiling playground v0.0.1 (/playground)
error[E0277]: cannot add `<D as sha2::Digest>::OutputSize` to `<D as sha2::Digest>::OutputSize`
--> src/lib.rs:13:21
|
13 | format!("{:x}", digest)
| ^^^^^^ no implementation for `<D as sha2::Digest>::OutputSize + <D as sha2::Digest>::OutputSize`
|
= help: the trait `std::ops::Add` is not implemented for `<D as sha2::Digest>::OutputSize`
= note: required because of the requirements on the impl of `std::fmt::LowerHex` for `digest::generic_array::GenericArray<u8, <D as sha2::Digest>::OutputSize>`
= note: required by `std::fmt::LowerHex::fmt`
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider further restricting the associated type
|
9 | fn compute_hash<D: Digest>(input_data: &str) -> String where <D as sha2::Digest>::OutputSize: std::ops::Add {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to previous error
For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground`.
Now suppose I understand the error correctly, the implementation of std::fmt::LowerHex
used by format!()
appears to require std::ops::Add
for the OutputSize
of the GenericArray<u8, N>
(i.e. the N
) which is returned by .finalize()
. However, the non-generic example suggests that there is such an implementation for ArrayLength<u8>
.
So, given I cannot implement the std::ops::Add
trait for an external type, how can I satisfy the compiler in this case?
Or maybe to rephrase my question, although I - being new with Rust - am not a 100% sure it is what I want: how do I tell the compiler to treat <D as sha2::Digest>::OutputSize
the same as ArrayLength<u8>
?
NB: I am relatively new with Rust, so please keep that in mind and kindly refer to the exact piece of documentation applicable to my case, rather than "the documentation" in general. I have scoured the documentation for digest
, for various of the implementers of digest::Digest
, for this error and for (what I thought were) similar issues and the traits topics in the Rust Book (2018 edition) for nearly three hours before I asked. Thanks.
In the second example I am using use digest::Digest;
. That's because in the future other hash algorithms are supposed to follow and it seemed to make more sense to use digest::Digest
directly instead of the re-exported Digest
from one of the implementers. If there is a reason against that, feel free to remark on it.
Rust requires that you specify all the functionality you use in generics. The note:
| ^^^^^^ no implementation for `<D as sha2::Digest>::OutputSize + <D as sha2::Digest>::OutputSize`
= help: the trait `std::ops::Add` is not implemented for `<D as sha2::Digest>::OutputSize`
is trying to say that we are using Add
on the type D::OutputSize
but not requiring it as a constraint, which we can do like so:
fn compute_hash<D: Digest>(input_data: &str) -> String
where D::OutputSize: std::ops::Add
If you make this change, you'll come to the next error:
| ^^^^^^ the trait `digest::generic_array::ArrayLength<u8>` is not implemented for `<<D as sha2::Digest>::OutputSize as std::ops::Add>::Output`
so there is another requirement, but we can add that too:
fn compute_hash<D: Digest>(input_data: &str) -> String
where D::OutputSize: std::ops::Add,
<D::OutputSize as std::ops::Add>::Output: digest::generic_array::ArrayLength<u8>
This will compile.
But let's dive into the reasons why these constraints are necessary. finalize
returns Output<D>
and we know that it is the type GenericArray<u8, <D as Digest>::OutputSize>
. Evidently format!("{:x}", ...)
requires the trait LowerHex
so we can see when this type satisfies this trait. See:
impl<T: ArrayLength<u8>> LowerHex for GenericArray<u8, T>
where
T: Add<T>,
<T as Add<T>>::Output: ArrayLength<u8>,
That looks familiar. So the return type of finalize
is satisfies LowerHex
if these constraints are true.
But we can get at the same thing more directly. We want to be able to format using LowerHex
and we can say that:
fn compute_hash<D: Digest>(input_data: &str) -> String
where digest::Output<D>: core::fmt::LowerHex
Since this can directly express what we use in the generic function, this seems preferable.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With