繁体   English   中英

没有类型参数的泛型类型上的泛型结构

[英]Generic struct over a generic type without type parameter

有可能在 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 这就是我想要为单线程和多线程使用一个结构的方式。

⇒ 这目前无法在 Rust 的类型系统中表达 ☹

幸运的是,由于本 RFC 中提出的“通用关联类型”,未来将成为可能。 您可以在相应的跟踪问题中跟踪实施和稳定状态。


这里最重要的词是“HKT”(H igherķinded牛逼YPES)。 这是尚未在 Rust 中实现的类型系统的一个特性。 Haskell 提供 HKT。 在 C++ 世界中,HKT 被称为“模板模板”。 上面提到的泛型关联类型也是 HKT 的一种形式。

但究竟什么是 HKT?

让我们慢慢开始:我们所知道的简单类型是什么? 让我们列出一些类型: i32boolString 这些都是类型……您可以拥有这些类型的值(变量)。 Vec<i32>呢? 这也是一种简单的类型! 你可以有一个Vec<i32>类型的变量,没问题!

我们想将这些类型组合在一起; 我们称这种分类为“某种类型”。 如果我们想以一种非常抽象的方式(关于类型的类型)进行讨论,我们会选择其他词,在这种情况下是kind 甚至还有类型的符号。 对于上面的简单类型,我们说:这些类型的种类是

*

是的,只是一颗星星,很容易。 符号在以后更有意义!


让我们搜索与简单类型不同的类型。 Mutex<HashMap<Vec<i32>, String>> ? 不,它可能相当复杂,但它仍然是那种*并且我们仍然可以拥有这种类型的变量。

Vec呢? 是的,我们省略了尖括号。 是的,这确实是另一种类型! 我们可以有一个Vec类型的变量吗? 不! 什么的向量?!

这种捐赠方式为:

* -> *

这只是说:给我一个普通类型( * ),我将返回一个普通类型! 给这个东西( Vec )一个普通类型i32 ,它会返回一个普通类型Vec<i32> 它也称为类型构造函数,因为它用于构造类型。 我们甚至可以更进一步:

* -> * -> *

这有点奇怪,因为它与柯里化有关,对于非 Haskell 程序员来说读起来很奇怪。 但这意味着:给我两种类型,我将返回一种类型。 让我们考虑一个例子...... Result 在您提供两个具体类型AB之后Result<A, B> Result类型构造函数将返回一个具体类型Result<A, B>

术语更高级的类型只是指所有不是*的类型,它们是类型构造函数。

在你的例子中

当您编写struct Bar<T: Foo>您希望T* -> * ,这意味着:您可以给T一个类型并接收一个简单类型。 但正如我所说,这在 Rust 中还不能表达。 要使用类似的语法,人们可能会想象这在未来可以工作:

// This does NOT WORK!
struct Bar<for<U> T> where T<U>: Foo {
    a: T<A>,
    b: T<B>,
}

for<>语法是从"higher-ranked trait bounds" (HRTB)借来的,它现在可用于对生命周期进行抽象(最常用于闭包)。

链接

如果您想阅读有关此主题的更多信息,请访问以下链接:


奖励:万一将实现关联类型构造函数,您的问题的解决方案(我认为,因为没有办法测试)!

我们必须在我们的实现中绕道而行,因为 RFC 不允许将Rc作为类型参数直接传递。 可以这么说,它没有直接介绍HKT。 但是正如 Niko 在他的博客文章中所说的那样,我们可以通过使用所谓的“家族特征”,获得与具有关联类型构造函数的 HKT 相同的灵活性和功能。

/// This trait will be implemented for marker types, which serve as
/// kind of a proxy to get the real type.
trait RefCountedFamily {
    /// An associated type constructor. `Ptr` is a type constructor, because
    /// it is generic over another type (kind * -> *).
    type Ptr<T>;
}

struct RcFamily;
impl RefCountedFamily for RcFamily {
    /// In this implementation we say that the type constructor to construct
    /// the pointer type is `Rc`.
    type Ptr<T> = Rc<T>;
}

struct ArcFamily;
impl RefCountedFamily for ArcFamily {
    type Ptr<T> = Arc<T>;
}

struct Graph<P: RefCountedFamily> {
    // Here we use the type constructor to build our types
    nodes: P::Ptr<Node>,
    edges: P::Ptr<Edge>,
}

// Using the type is a bit awkward though:
type MultiThreadedGraph = Graph<ArcFamily>;

有关更多信息,您应该真正阅读 Niko 的博客文章。 困难的话题解释得足够好,甚至我都能或多或少地理解它们!

编辑:我刚刚注意到 Niko 在他的博客文章中实际上使用了Arc / Rc示例! 我完全忘记了这一点,我自己想到了上面的代码……但也许我的潜意识还记得,因为我和 Niko 一样选择了几个名字。 无论如何,这是他(可能更好)对这个问题的看法

在某种程度上,Rust确实看起来很像 HKT(请参阅 Lukas 的回答以了解它们是什么),尽管有一些可以说是笨拙的语法。

首先,您需要为所需的指针类型定义接口,这可以使用泛型特征来完成。 例如:

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特征的虚拟类型。 这是关键部分; 我们可以有一个类型 ( 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);
}

操场

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM