计算散列(digest::Digest 特征)并返回字符串的通用函数

Generic function to compute a hash (digest::Digest trait) and get back a String

我在解决这个问题时遇到了一些麻烦。我正在尝试编写一个通用函数,它可以接受任何 digest::Digest 并吐出计算摘要的字符串形式(“十六进制字符串”)。

这里是 the non-generic version 作为最小的例子:

#![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);
}

... 这是我的尝试 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);
}

... 出现以下错误:

   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`.

现在假设我正确地理解了错误,format!() 使用的 std::fmt::LowerHex 的实现似乎 需要 std::ops::Add 作为 [=由 .finalize() 返回的 GenericArray<u8, N>(即 N)的 17=]。但是,非通用示例表明 ArrayLength<u8>.

存在这样的实现

所以,鉴于我无法为外部类型实现 std::ops::Add 特性,在这种情况下我如何满足编译器的要求?

或者换个说法,虽然我是 Rust 的新手,但我不能 100% 确定这就是我想要的:我如何告诉编译器将 <D as sha2::Digest>::OutputSizeArrayLength<u8>?

注意: 我对 Rust 比较陌生,所以请记住这一点,并请参考 exact 的适用文档就我而言,而不是一般的“文档”。我已经搜索了 digest 的文档,digest::Digest 的各种实现者,这个错误和(我认为是)类似的问题以及 Rust Book(2018 版)中的特征主题在我问之前将近三个小时。谢谢


在第二个例子中我使用了use digest::Digest;。那是因为在未来其他哈希算法应该遵循并且直接使用 digest::Digest 而不是从实现者之一重新导出 Digest 似乎更有意义。如果有理由反对,请随时评论。

So, given I cannot implement the std::ops::Add trait for an external type, how can I satisfy the compiler in this case?

您不需要实现它,您需要实现它。

how do I tell the compiler to treat <D as sha2::Digest>::OutputSize the same as ArrayLength<u8>?

这个想法是对的,但是由于ArrayLength是一个特征,而不是一个类型,你想要“被强制执行”,而不是“相同”。

这将编译:

#![feature(associated_type_bounds)]

...

fn compute_hash<D>(input_data: &str) -> String
where
    D: Digest,
    D::OutputSize: ArrayLength<u8> + std::ops::Add<Output: ArrayLength<u8>>,
{

问题在于它使用了不稳定的特性 associated_type_bounds — 这是关联类型 D::OutputSize 出现在特征边界 SomeType: SomeTrait 左侧的部分。

为了坚持稳定的 Rust,我们可以引入一些丑陋的类型参数,通过使它们 equality 来避免关联的类型边界:

fn compute_hash<D, L1, L2>(input_data: &str) -> String
where
    D: Digest<OutputSize = L1>,
    L1: ArrayLength<u8> + std::ops::Add<Output = L2>,
    L2: ArrayLength<u8>,
{
    ...
}

fn main() {
    let s = compute_hash::<Sha256, _, _>("String data");
    println!("Result: {}", s);
}

但这需要在每个调用点写, _, _>

可能有更好的方法来做到这一点——我对 Rust 还很陌生,对 type-level 编程 ArrayLength 在 Rust 中所做的那种来龙去脉没有经验.

Rust 要求您指定在泛型中使用的所有功能。备注:

   |                     ^^^^^^ 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`

试图说我们在类型 D::OutputSize 上使用 Add 但不需要它作为约束,我们可以这样做:

fn compute_hash<D: Digest>(input_data: &str) -> String
    where D::OutputSize: std::ops::Add

如果您进行此更改,您将遇到下一个错误:

   |                     ^^^^^^ the trait `digest::generic_array::ArrayLength<u8>` is not implemented for `<<D as sha2::Digest>::OutputSize as std::ops::Add>::Output`

所以还有一个要求,但我们也可以添加:

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>

这将编译。

但让我们深入探讨为什么需要这些约束的原因。 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>, 

这看起来很眼熟。所以 finalize 的 return 类型满足 LowerHex 如果这些约束为真。

但我们可以更直接地了解同一件事。我们希望能够使用 LowerHexwe can say that:

进行格式化
fn compute_hash<D: Digest>(input_data: &str) -> String
    where digest::Output<D>: core::fmt::LowerHex

因为这可以直接表达我们在泛型函数中使用的东西,所以这似乎更可取。