没有类型参数的泛型类型的通用结构

tor*_*eyy 9 generics higher-kinded-types rust

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

trait Foo<T> {}

struct A;
struct B;

struct Bar<T: Foo> {
    a: T<A>,
    b: T<B>
}
Run Code Online (Sandbox Code Playgroud)

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

我想实现一个Graph结构.因为我不能将节点和边缘绑定到父母的生命周期,我希望有类似的东西Rc.但是,有时可能需要Graph具有多个线程的访问权限.所以我必须同时使用RcArc.

Foo是有益的:我实现Foo两者RcArc(Foo将需要Deref)并使用T绑定的参数Foo.这就是我想要一个结构用于单线程和多线程使用的方式.

Luk*_*odt 17

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

幸运的是,由于本RFC中提出的"通用关联类型",将来有可能实现.您可以在相应的跟踪问题中跟踪实施和稳定的状态.


这里最重要的词是"HKT"(^ h igher ķ inded 牛逼 YPES).这是类型系统的一个功能,尚未在Rust中实现.Haskell提供HKT.在C++世界中,HKT被称为"模板模板".上述通用相关类型也是HKT的一种形式.

但究竟HKT是什么?

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

我们想把这些类型组合在一起; 我们称这种分类为" 一种类型".如果我们想在一个非常抽象的方式谈论(约种类型),我们选择换言之,那种在这种情况下.甚至还有一个符号种类的类型.对于我们上面的简单类型,我们说:那些类型的类型是

*
Run Code Online (Sandbox Code Playgroud)

是的,只是一个明星,非常容易.这种符号后来更有意义!


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

怎么样Vec?是的,我们省略了角括号.是的,这确实是另一种类型!我们可以有一个类型的变量Vec吗?没有!什么的矢量?!

这种捐赠为:

* -> *
Run Code Online (Sandbox Code Playgroud)

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

* -> * -> *
Run Code Online (Sandbox Code Playgroud)

这有点奇怪,因为它与curry有关,读取非Haskell程序员的奇数.但它意味着:给我两种类型,我将返回一种类型.让我们考虑一个例子...... Result!该Result类型的构造函数会返回一个具体类型Result<A, B>你提供了两种具体类型后AB.

术语较高的kinded类型只是指所有不*属于类型构造函数的类型.

在你的例子中

当你写作时,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>,
}
Run Code Online (Sandbox Code Playgroud)

for<>语法是从借来的"高排名特质界限"(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>;
Run Code Online (Sandbox Code Playgroud)

有关更多信息,您应该阅读Niko的博客文章.困难的主题解释得很好,即使我可以或多或少地理解它们!

编辑:我刚刚注意到Niko实际上在他的博客文章中使用了Arc/ Rcexample!我完全忘记了并且想到了我自己上面的代码......但也许我的潜意识仍然被记住,因为我选择了几个与Niko完全相同的名字.无论如何,这是他(可能更好)对这个问题的看法.


Chr*_*son 6

在某种程度上Rust 确实看起来很像HKT(参见Lukas的答案,可以很好地描述它们是什么),尽管有一些可以说是笨拙的语法.

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

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

加上一个通用特征,它定义了一个你真正想要的类型的关联类型,它必须实现你的接口:

trait Param<T> {
    type Pointer: SharedPointer<T>;
}
Run Code Online (Sandbox Code Playgroud)

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

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

并定义一些实现上述Param特征的虚拟类型.这是关键部分; 我们可以有一个type(RcParam)实现Param<T>任何类型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>;
}
Run Code Online (Sandbox Code Playgroud)

最后我们可以使用它:

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);
}
Run Code Online (Sandbox Code Playgroud)

操场