没有类型参数的泛型类型上的泛型结构
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
来从多个线程访问。所以我必须同时使用 Rc
和 Arc
.
来实现
这就是 Foo
的好处:我为 Rc
和 Arc
实现了 Foo
(Foo
需要 Deref
)我使用绑定到 Foo
的参数 T
。这就是我想要一个结构用于单线程和多线程使用的方式。
⇒ 这 目前不可能 在 Rust 的类型系统中表达 ☹
幸运的是,由于 this RFC. You can track the status of implementation and stabilization in the corresponding tracking issue 中提出的 "Generic Associated Types",将来有可能实现。
这里的重要术语是"HKT" (higher kinded t类型)。它是类型系统的一个特性,尚未在 Rust 中实现。 Haskell 提供 HKT。在 C++ 世界中,HKT 被称为 "template templates"。上面提到的泛型关联类型也是HKT的一种形式。
但是 HKT 到底是什么?
我们慢慢来:什么是我们所知道的简单类型?让我们列出一些类型:i32
、bool
、String
。这些都是类型……您可以拥有这些类型的值(变量)。 Vec<i32>
呢?这也是一种简单的类型!你可以有一个 Vec<i32>
类型的变量,没问题!
我们想将这些类型组合在一起;我们称这种分类为“种类 类型”。如果我们想以非常抽象的方式(关于类型的类型)进行讨论,我们会选择其他词,在本例中为 kind。甚至还有 种 类型的表示法。对于上面的简单类型,我们说:这些类型的种类是
*
是的,一个星,很简单。该符号稍后会更有意义!
让我们搜索与简单类型不同的类型。 Mutex<HashMap<Vec<i32>, String>>
?不,它可能相当复杂,但它仍然很好 *
我们仍然可以拥有那种类型的变量。
那Vec
呢?是的,我们省略了尖括号。是的,这确实是另一种类型!我们可以有一个 Vec
类型的变量吗?不! what?!
的向量
这种捐赠为:
* -> *
这只是说:给我一个普通类型(*
),我就return一个普通类型!给这个东西(Vec
)一个普通类型i32
,它就会return一个普通类型Vec<i32>
!它也被称为类型构造函数,因为它用于构造类型。我们甚至可以更进一步:
* -> * -> *
这有点奇怪,因为它与 currying 有关并且对于非 Haskell 程序员来说读起来很奇怪。但这意味着:给我 两个 类型,我将 return 一个类型。让我们考虑一个例子...... Result
!在您提供了两个具体类型 A
和 B
.
之后,Result
类型构造函数将 return 一个具体类型 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) 借用的,今天可用于对生命周期进行抽象(最常与闭包一起使用)。
链接
如果您想阅读更多关于这个主题的信息,这里有一些链接:
- Niko Matsakis' great series of blog posts discussing one possible solution (associated type constructors) to the HKT problem
- The RFC proposing generic associated types (just a less scary name for "associated type constructors")
奖励:在关联类型构造函数将被实现的情况下解决你的问题(我认为,因为没有办法测试)!
我们必须在实现中绕道而行,因为 RFC 不允许直接将 Rc
作为类型参数传递。可以这么说,它没有直接介绍 HKT。但正如 Niko 在他的博客 post 中所论证的那样,通过使用所谓的 "family traits".
,我们可以拥有与具有关联类型构造函数的 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 的博客 posts。难点的题目解释得很好,连我都能或多或少地看懂!
编辑:我刚刚注意到 Niko 实际上在他的博客 post 中使用了 Arc
/Rc
示例!我完全忘记了这一点,想到了上面的代码……但也许我的潜意识还记得,因为我选择了几个名字,就像 Niko 所做的那样。无论如何,这里是 his (probably way better) take on the issue.
在某种程度上,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);
}
在 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
的好处:我为 Rc
和 Arc
实现了 Foo
(Foo
需要 Deref
)我使用绑定到 Foo
的参数 T
。这就是我想要一个结构用于单线程和多线程使用的方式。
⇒ 这 目前不可能 在 Rust 的类型系统中表达 ☹
幸运的是,由于 this RFC. You can track the status of implementation and stabilization in the corresponding tracking issue 中提出的 "Generic Associated Types",将来有可能实现。
这里的重要术语是"HKT" (higher kinded t类型)。它是类型系统的一个特性,尚未在 Rust 中实现。 Haskell 提供 HKT。在 C++ 世界中,HKT 被称为 "template templates"。上面提到的泛型关联类型也是HKT的一种形式。
但是 HKT 到底是什么?
我们慢慢来:什么是我们所知道的简单类型?让我们列出一些类型:i32
、bool
、String
。这些都是类型……您可以拥有这些类型的值(变量)。 Vec<i32>
呢?这也是一种简单的类型!你可以有一个 Vec<i32>
类型的变量,没问题!
我们想将这些类型组合在一起;我们称这种分类为“种类 类型”。如果我们想以非常抽象的方式(关于类型的类型)进行讨论,我们会选择其他词,在本例中为 kind。甚至还有 种 类型的表示法。对于上面的简单类型,我们说:这些类型的种类是
*
是的,一个星,很简单。该符号稍后会更有意义!
让我们搜索与简单类型不同的类型。 Mutex<HashMap<Vec<i32>, String>>
?不,它可能相当复杂,但它仍然很好 *
我们仍然可以拥有那种类型的变量。
那Vec
呢?是的,我们省略了尖括号。是的,这确实是另一种类型!我们可以有一个 Vec
类型的变量吗?不! what?!
这种捐赠为:
* -> *
这只是说:给我一个普通类型(*
),我就return一个普通类型!给这个东西(Vec
)一个普通类型i32
,它就会return一个普通类型Vec<i32>
!它也被称为类型构造函数,因为它用于构造类型。我们甚至可以更进一步:
* -> * -> *
这有点奇怪,因为它与 currying 有关并且对于非 Haskell 程序员来说读起来很奇怪。但这意味着:给我 两个 类型,我将 return 一个类型。让我们考虑一个例子...... Result
!在您提供了两个具体类型 A
和 B
.
Result
类型构造函数将 return 一个具体类型 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) 借用的,今天可用于对生命周期进行抽象(最常与闭包一起使用)。
链接
如果您想阅读更多关于这个主题的信息,这里有一些链接:
- Niko Matsakis' great series of blog posts discussing one possible solution (associated type constructors) to the HKT problem
- The RFC proposing generic associated types (just a less scary name for "associated type constructors")
奖励:在关联类型构造函数将被实现的情况下解决你的问题(我认为,因为没有办法测试)!
我们必须在实现中绕道而行,因为 RFC 不允许直接将 Rc
作为类型参数传递。可以这么说,它没有直接介绍 HKT。但正如 Niko 在他的博客 post 中所论证的那样,通过使用所谓的 "family traits".
/// 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 的博客 posts。难点的题目解释得很好,连我都能或多或少地看懂!
编辑:我刚刚注意到 Niko 实际上在他的博客 post 中使用了 Arc
/Rc
示例!我完全忘记了这一点,想到了上面的代码……但也许我的潜意识还记得,因为我选择了几个名字,就像 Niko 所做的那样。无论如何,这里是 his (probably way better) take on the issue.
在某种程度上,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);
}