Rust 泛型/特征:`from(...)` 中的不匹配类型和一般设计问题

问题描述 投票:0回答:2

我对 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 中是否有更好的方法来实现这种通用工厂方案或样板代码避免策略?

generics rust traits
2个回答
1
投票

这确实是一个难以理解的案例,错误信息令人困惑。但是,如果编译器的消息让您感到困惑,请尝试通过添加类型注释来帮助它。在这种情况下,如果我们显式注释我们要使用的

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


0
投票

您的大部分资格实际上并不是必需的,仅当编译器无法推断 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());
    
© www.soinside.com 2019 - 2024. All rights reserved.