【问题标题】:Generic function to compute a hash (digest::Digest trait) and get back a String计算散列(digest::Digest trait)并取回字符串的通用函数
【发布时间】:2020-10-13 20:06:05
【问题描述】:

我在解决这个问题时遇到了一些麻烦。我正在尝试编写一个通用函数,它可以采用任何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 用于GenericArray&lt;u8, N&gt;OutputSize(即@987654333 @) 由.finalize() 返回。但是,非通用示例表明ArrayLength&lt;u8&gt; 存在这样的实现。

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

或者改写我的问题,虽然我 - 刚接触 Rust - 并不是 100% 确定这是我想要的:我如何告诉编译器将 &lt;D as sha2::Digest&gt;::OutputSizeArrayLength&lt;u8&gt; 一样对待?

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


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

【问题讨论】:

  • 可能值得注意的是digest crate 使用generic_array 来解决const generics 缺乏语言支持的问题。该语言的未来版本似乎可能允许digest 以更直接的方式支持这种用法。

标签: generics rust traits


【解决方案1】:

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 返回Output&lt;D&gt;,我们知道它是GenericArray&lt;u8, &lt;D as Digest&gt;::OutputSize&gt; 类型。显然format!("{:x}", ...) 需要特征LowerHex,所以我们可以看到这个类型何时满足这个特征。 See:

impl<T: ArrayLength<u8>> LowerHex for GenericArray<u8, T>
where
    T: Add<T>,
    <T as Add<T>>::Output: ArrayLength<u8>, 

这看起来很熟悉。所以finalize的返回类型满足LowerHex如果这些约束是真的。

但我们可以更直接地处理同一件事。我们希望能够使用LowerHexwe can say that 进行格式化:

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

由于这可以直接表达我们在泛型函数中使用的内容,这似乎更可取。

【讨论】:

  • 非常感谢。作为 Rust 的学生,我对解决方案的路径尤其感兴趣。
【解决方案2】:

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

你不需要实现它,你需要要求它实现。

如何告诉编译器将&lt;D as sha2::Digest&gt;::OutputSizeArrayLength&lt;u8&gt; 同等对待?

这是正确的想法,但由于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,我们可以引入一些丑陋的类型参数来避免关联的类型边界,方法是让它们相等

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

但这需要在每个呼叫站点写, _, _&gt;

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-30
    • 1970-01-01
    • 1970-01-01
    • 2014-02-06
    • 1970-01-01
    相关资源
    最近更新 更多