可以在Rust中做这样的事吗?
trait Foo<T> {}
struct A;
struct B;
struct Bar<T: Foo> {
a: T<A>,
b: T<B>
}
我知道我可以为Bar
使用两个参数,但我认为必须有更好的方法来做到这一点。
我想实现Graph
结构。由于我无法将节点和边缘绑定到父母的生命周期,因此我想要Rc
之类的东西。但是,有时可能需要Graph
来自多个线程的访问权限。因此,我必须同时拥有Rc
和Arc
的实施方案。
Foo
有什么好处:我为Foo
和Rc
实施Arc
(Foo
需要Deref
我使用绑定到T
的参数Foo
。这就是我想要一个结构用于单线程和多线程使用的方法。
答案 0 :(得分:16)
⇒这是目前不可能在Rust的类型系统中表达☹
幸运的是,由于this RFC中提出的“通用关联类型”,将来有可能实现。您可以在the corresponding tracking issue。
中跟踪实施和稳定的状态这里的重要术语是“HKT”( h 更高 k inded t ypes )。这是类型系统的一个功能,尚未在Rust中实现。 Haskell提供HKT。在C ++世界中,HKT被称为“模板模板”。上面提到的通用关联类型也是HKT的一种形式。
让我们慢慢开始:我们知道什么是简单类型?我们列出一些类型:i32
,bool
,String
。这些都是类型......您可以拥有这些类型的值(变量)。那么Vec<i32>
呢?这也是一个简单的类型!您可以拥有Vec<i32>
类型的变量,没问题!
我们希望将这些类型组合在一起;我们将此分类称为“种类型”。如果我们想以一种非常抽象的方式(关于类型的类型)进行交谈,我们在这种情况下选择其他词语 kind 。甚至还有种类型的表示法。对于我们上面的简单类型,我们说:那些类型的类型是
*
是的,只是一个明星,很容易。这个符号后来更有意义了!
让我们搜索与我们的简单类型不同的类型。 Mutex<HashMap<Vec<i32>, String>>
?不,它可能相当复杂,但它仍然是善良*
,我们仍然可以拥有该类型的变量。
Vec
怎么样?是的,我们省略了角括号。是的,这确实是另一种类型!我们可以使用Vec
类型的变量吗?没有! what 的向量?!
这种捐赠为:
* -> *
这只是说:给我一个普通的类型(*
),我将返回一个普通类型!为此事物i32
提供正常类型Vec
,它将返回普通类型Vec<i32>
!它也被称为类型构造函数,因为它用于构造类型。我们甚至可以走得更远:
* -> * -> *
这有点奇怪,因为它与currying有关,而对于非Haskell程序员则读取奇数。但它意味着:给我两个类型,我将返回一个类型。让我们考虑一个例子...... Result
!在提供两个具体类型Result
和Result<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>;
}