我对 Rust 很陌生,尤其是它的类型系统,并且正在努力解决以下(简化的)问题。
use std::collections::HashSet;
trait Foo {
type Container;
fn new(values: Self::Container) -> Self;
}
fn create_foo<T: Foo>(c: <T as Foo>::Container) -> T {
T::new(c)
}
struct Bar<A> {
values: <Bar<A> as Foo>::Container
}
impl<A> Foo for Bar<A> {
type Container = HashSet<A>;
fn new(values: <Self as Foo>::Container) -> Self {
Self{values}
}
}
fn main() {
type BarI32 = Bar<i32>;
let bi32: BarI32 = create_foo(<BarI32 as Foo>::Container::from([1, 2, 3]));
type BarU8 = Bar<u8>;
let bu8: BarU8 = create_foo(<BarU8 as Foo>::Container::from([1, 2, 3]));
}
这按预期工作,但重复的
type BarX = Bar<X>
和 <BarX as Foo>::Container::from
是很多样板代码。所以我的第一个问题是如何在没有样板代码的情况下使它更紧凑?我的尝试是这样的:
fn create_bar<T, const N: usize>(a: [T; N]) -> Bar<T> {
return create_foo(<Bar<T> as Foo>::Container::from(a))
}
fn main() {
let bi32: Bar<i32> = create_bar( [1, 2, 3]);
let bu8: Bar<u8> = create_bar([1, 2, 3]);
}
根据我的理解,我们需要
create_bar
并且不能再使用create_foo<T: Foo>
因为::from(...)
仅适用于特定容器(这里HashSet
)但我现在得到以下错误:
error[E0308]: mismatched types
--> src/main.rs:31:56
|
31 | return create_foo(<Bar<T> as Foo>::Container::from(a))
| -------------------------------- ^ expected struct `HashSet`, found array
| |
| arguments to this function are incorrect
|
= note: expected struct `HashSet<T>`
found array `[T; N]`
note: associated function defined here
--> /home/xxx/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/convert/mod.rs:376:8
|
376 | fn from(_: T) -> Self;
| ^^^^
为什么它现在在
from
中使用通用转换 trait From<T>
而不再是 impl<T, const N: usize> From<[T; N]> for HashSet<T, RandomState>
并且如何解决这个问题?这与某些 impl<A> Foo for Bar<A>
could 可以实现以使用不同的容器类型有关吗?
除了这个问题,在 Rust 中是否有更好的方法来实现这种通用工厂方案或样板代码避免策略?
这确实是一个难以理解的案例,错误信息令人困惑。但是,如果编译器的消息让您感到困惑,请尝试通过添加类型注释来帮助它。在这种情况下,如果我们显式注释我们要使用的
From
impl,编译器会更有帮助:
return create_foo(<<Bar<T> as Foo>::Container as From<[T; N]>>::from(a));
error[E0277]: the trait bound `T: Eq` is not satisfied
--> src/main.rs:24:74
|
24 | return create_foo(<<Bar<T> as Foo>::Container as From<[T; N]>>::from(a));
| -------------------------------------------------- ^ the trait `Eq` is not implemented for `T`
| |
| required by a bound introduced by this call
|
= note: required for `HashSet<T>` to implement `From<[T; N]>`
help: consider restricting type parameter `T`
|
23 | fn create_bar<T: std::cmp::Eq, const N: usize>(a: [T; N]) -> Bar<T> {
| ++++++++++++++
error[E0277]: the trait bound `T: Hash` is not satisfied
--> src/main.rs:24:74
|
24 | return create_foo(<<Bar<T> as Foo>::Container as From<[T; N]>>::from(a));
| -------------------------------------------------- ^ the trait `Hash` is not implemented for `T`
| |
| required by a bound introduced by this call
|
= note: required for `HashSet<T>` to implement `From<[T; N]>`
help: consider restricting type parameter `T`
|
23 | fn create_bar<T: std::hash::Hash, const N: usize>(a: [T; N]) -> Bar<T> {
| +++++++++++++++++
现在可能已经很清楚了。如果我们查看 我们想要调用的
From
impl:
impl<T, const N: usize> From<[T; N]> for HashSet<T, RandomState>
where
T: Eq + Hash,
所以它需要
T
来实现Hash
和Eq
(因为HashSet
需要它)。当我们不是泛型时,类型实现了它们,但现在我们是泛型的,我们需要一个约束:
fn create_bar<T: std::hash::Hash + Eq, const N: usize>(a: [T; N]) -> Bar<T> {
create_foo(<Bar<T> as Foo>::Container::from(a))
}
它有效。
发生的事情是编译器试图提供帮助,因为我们想要的
From
impl 不匹配,它查看了其他 impl。唯一满足约束条件的实现是一揽子实施impl<T> From<T> for T
,所以它抱怨我们没有通过HashSet
。
您的大部分资格实际上并不是必需的,仅当编译器无法推断 T 应该用作什么(因为它不明确)时才需要
<T as U>::
方案。但这里唯一需要的地方是Bar<A>
的定义:
在
create_foo
的定义中,只有一个 ::Container
可能与 T
相关联,因为 T
具有单一特征界限,因此
fn create_foo<T: Foo>(c: T::Container) -> T
工作正常
与
new
中impl Foo
的定义相同(实际上更是如此)
fn new(values: Self::Container)
当using
create_foo
你可以直接引用trait本身,它不会产生歧义,因为“输出”类型严格限制了输入,所以
let bi32: BarI32 = create_foo(From::from([1, 2, 3]));
做的伎俩,或
let bi32: BarI32 = create_foo([1, 2, 3].into());