没有类型参数

时间:2017-01-06 15:18:47

标签: generics rust higher-kinded-types

可以在Rust中做这样的事吗?

trait Foo<T> {}

struct A;
struct B;

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

我知道我可以为Bar使用两个参数,但我认为必须有更好的方法来做到这一点。

我想实现Graph结构。由于我无法将节点和边缘绑定到父母的生命周期,因此我想要Rc之类的东西。但是,有时可能需要Graph来自多个线程的访问权限。因此,我必须同时拥有RcArc的实施方案。

Foo有什么好处:我为FooRc实施ArcFoo需要Deref我使用绑定到T的参数Foo。这就是我想要一个结构用于单线程和多线程使用的方法。

2 个答案:

答案 0 :(得分:16)

⇒这是目前不可能在Rust的类型系统中表达☹

幸运的是,由于this RFC中提出的“通用关联类型”,将来有可能实现。您可以在the corresponding tracking issue

中跟踪实施和稳定的状态

这里的重要术语是“HKT”( h 更高 k inded t ypes )。这是类型系统的一个功能,尚未在Rust中实现。 Haskell提供HKT。在C ++世界中,HKT被称为“模板模板”。上面提到的通用关联类型也是HKT的一种形式。

但HKTs究竟是什么?

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

我们希望将这些类型组合在一起;我们将此分类称为“类型”。如果我们想以一种非常抽象的方式(关于类型的类型)进行交谈,我们在这种情况下选择其他词语 kind 。甚至还有类型的表示法。对于我们上面的简单类型,我们说:那些类型的类型是

*

是的,只是一个明星,很容易。这个符号后来更有意义了!

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

Vec怎么样?是的,我们省略了角括号。是的,这确实是另一种类型!我们可以使用Vec类型的变量吗?没有! what 的向量?!

这种捐赠为:

* -> *

这只是说:给我一个普通的类型(*),我将返回一个普通类型!为此事物i32提供正常类型Vec,它将返回普通类型Vec<i32>!它也被称为类型构造函数,因为它用于构造类型。我们甚至可以走得更远:

* -> * -> *

这有点奇怪,因为它与currying有关,而对于非Haskell程序员则读取奇数。但它意味着:给我两个类型,我将返回一个类型。让我们考虑一个例子...... Result!在提供两个具体类型ResultResult<A, B>后,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作为类型参数传递。它没有直接介绍HKTs,可以这么说。但正如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完全相同的名字。无论如何,这是his (probably way better) take on the issue

答案 1 :(得分:6)

在某种程度上,Rust 看起来很像HKT(请参阅Lukas的回答,以便对它们的描述有一个很好的描述),尽管有一些可疑的笨拙语法。

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

ungetc()

另外还有一个通用特征,它定义了一个你真正想要的类型的关联类型,它必须实现你的界面:

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实现Param<T>,包括能够提供类型,这意味着我们正在模拟更高级的类型。< / p>

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>;
}

Playground