没有类型参数的通用类型上的通用结构体

17
在Rust中是否可以做到像这样的事情?
trait Foo<T> {}

struct A;
struct B;

struct Bar<T: Foo> {
    a: T<A>,
    b: T<B>
}

我知道我可以只使用两个参数来定义Bar,但我认为一定有更好的方法来实现这个功能。

我想要实现一个Graph结构。由于我不能仅仅将节点和边绑定到它们父级的生命周期上,所以我想要像Rc一样实现一个类似的结构。然而,有时候我们可能需要一个可以从多个线程访问的Graph。因此,我需要同时实现RcArc的版本。

这就是Foo的作用:我为RcArc分别实现FooFoo需要Deref),并使用一个绑定到Foo的参数T。这样我就可以拥有一个适用于单线程和多线程的结构体。

2个回答

25
你可以使用通用关联类型(GATs)和家族模式来实现这一点。
trait Family {
    type Container<T>;
}

struct A;
struct B;

struct Bar<T: Family> {
    a: T::Container<A>,
    b: T::Container<B>,
}

然后你可以定义两个家族:
struct BoxFamily;
impl Family for BoxFamily {
    type Container<T> = Box<T>;
}

struct VecFamily;
impl Family for VecFamily {
    type Container<T> = Vec<T>;
}

然后使用它:

let boxes: Bar<BoxFamily> = Bar {
    a: Box::new(A),
    b: Box::new(B),
};

let vecs: Bar<VecFamily> = Bar {
    a: vec![A, A],
    b: vec![B],
};

游乐场
如你所见,这比人们希望的要复杂一些:例如,你不能只说Bar<Vec>,而必须经过额外的家族类型。但它有效!
要查看更多关于该主题的旧回答(在GATs存在之前),请点击这里

1
谢谢您提供这么详细的回答!非常有趣。只有 for 语法对我来说有点困惑,但我一定会阅读相关章节。 - torkleyy
谢谢。解释得足够清楚了。我认为真正的力量来自于关联类型的类型省略。 - Ricky Han

8

在某种程度上,Rust 确实有着很像 HKT 的东西(请参考 Lukas 的答案以了解它们的描述),但语法可能有些别扭。

首先,你需要定义所需指针类型的接口,可以使用一个通用 trait 来完成。例如:

trait SharedPointer<T>: Clone {
    fn new(v: T) -> Self;
    // more, eg: fn get(&self) -> &T;
}

此外,还有一个通用的特征,它定义了一个关联类型,这个类型是你真正想要的类型,并且必须实现你的接口:

trait Param<T> {
    type Pointer: SharedPointer<T>;
}

接下来,我们为我们感兴趣的类型实现该接口:

impl<T> SharedPointer<T> for Rc<T> {
    fn new(v: T) -> Self {
        Rc::new(v)
    }
}
impl<T> SharedPointer<T> for Arc<T> {
    fn new(v: T) -> Self {
        Arc::new(v)
    }
}

定义一些虚拟类型,这些类型实现了上面提到的Param trait。这是关键部分;我们可以有一个类型(RcParam),它为任何T实现Param<T>,包括能够提供类型,这意味着我们在模拟高阶类型。

struct RcParam;
struct ArcParam;

impl<T> Param<T> for RcParam {
    type Pointer = Rc<T>;
}

impl<T> Param<T> for ArcParam {
    type Pointer = Arc<T>;
}

最后,我们可以使用它:

struct A;
struct B;

struct Foo<P: Param<A> + Param<B>> {
    a: <P as Param<A>>::Pointer,
    b: <P as Param<B>>::Pointer,
}

impl<P: Param<A> + Param<B>> Foo<P> {
    fn new(a: A, b: B) -> Foo<P> {
        Foo {
            a: <P as Param<A>>::Pointer::new(a),
            b: <P as Param<B>>::Pointer::new(b),
        }
    }
}

fn main() {
    // Look ma, we're using a generic smart pointer type!
    let foo = Foo::<RcParam>::new(A, B);
    let afoo = Foo::<ArcParam>::new(A, B);
}

游乐场


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接