计算散列(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>::OutputSize
与 ArrayLength<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
如果这些约束为真。
但我们可以更直接地了解同一件事。我们希望能够使用 LowerHex
和 we can say that:
进行格式化
fn compute_hash<D: Digest>(input_data: &str) -> String
where digest::Output<D>: core::fmt::LowerHex
因为这可以直接表达我们在泛型函数中使用的东西,所以这似乎更可取。
我在解决这个问题时遇到了一些麻烦。我正在尝试编写一个通用函数,它可以接受任何 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>::OutputSize
与 ArrayLength<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 asArrayLength<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
如果这些约束为真。
但我们可以更直接地了解同一件事。我们希望能够使用 LowerHex
和 we can say that:
fn compute_hash<D: Digest>(input_data: &str) -> String
where digest::Output<D>: core::fmt::LowerHex
因为这可以直接表达我们在泛型函数中使用的东西,所以这似乎更可取。