简体   繁体   English

有没有办法模拟Rust中的通用关联类型/关联类型构造函数?

[英]Is there any way to simulate Generic Associated Types / Associated Type Constructors in Rust?

I'm making a graph processing module in Rust. 我正在Rust中制作一个图形处理模块。 The core of the module models the idea of having multiple containers which hold the data in the graph. 该模块的核心模型是拥有多个容器的想法,这些容器将数据保存在图表中。 For example, I may have a graph whose inner structure is an HashMap or maybe AdjacencyMatrix etc. 例如,我可能有一个图形,其内部结构是HashMap或者AdjacencyMatrix等。

These containers must implement a trait: 这些容器必须实现一个特性:

trait GraphData<V> {
    fn has_edge(&self, v: &V, u: &V) -> bool;
    fn nodes(&self) -> Iterator<V>; // Here's the problem...
}

I can't just return a trait in my trait definition. 我不能只在我的特质定义中返回一个特征。 I know I must use trait object, but I don't want to Box it. 我知道我必须使用特质对象,但我不想把它Box I would like to make the container provide its own NodeIter struct. 我想让容器提供自己的NodeIter结构。 However, I would be stuck with the same problem explained in Associated type constructors, part 1: basic concepts and introduction . 但是,我会遇到在Associated类型构造函数中解释的相同问题,第1部分:基本概念和介绍 The post explains about associated type constructors (ATC) which do not exist in Rust now. 该帖子解释了现在Rust中不存在的相关类型构造函数(ATC)。 My GraphData resembles the generic Collection described. 我的GraphData类似于所描述的通用Collection

Is there any workaround I could use to "simulate" ATC or any pattern specific to Rust I could use for this situation? 是否有任何解决方法我可以用来“模拟”ATC或任何特定于Rust的模式我可以用于这种情况?

I don't want to depend on dynamic dispatch and resort to using Box or the dyn keyword. 我不想依赖动态调度并使用Boxdyn关键字。

I thought to define a struct NodeIter for each type of graph container I created in my module and add "nodes" inside the implementation of the container itself. 我想为我在模块中创建的每种类型的图形容器定义一个struct NodeIter ,并在容器本身的实现中添加“nodes”。 However, I find this to be poor code reuse. 但是,我发现这是糟糕的代码重用。

The problem you've described is solved with plain associated types . 您描述的问题通过普通关联类型解决。 It does not require generic associated types , aka associated type constructors. 它不需要通用的关联类型 ,也就是相关的类型构造函数。 This already works in stable Rust. 这已经适用于稳定的Rust。

trait GraphData<V> {
    type Nodes: Iterator<Item = V>;
    fn has_edge(&self, v: &V, u: &V) -> bool;
    fn nodes(&self) -> Self::Nodes;
}

struct Graph<V> {
    nodes: Vec<V>,
    edges: Vec<(V, V)>,
}

impl<V: Clone + Eq> GraphData<V> for Graph<V> {
    type Nodes = vec::IntoIter<V>;
    fn has_edge(&self, u: &V, v: &V) -> bool {
        self.edges.iter().any(|(u1, v1)| u == u1 && v == v1)
    }
    fn nodes(&self) -> Self::Nodes {
        self.nodes.clone().into_iter()
    }
}

Nodes has no type or lifetime parameters (it's not Nodes<T> or Nodes<'a> ), so it's not generic. Nodes没有类型或生命周期参数(它不是Nodes<T>Nodes<'a> ),因此它不是通用的。

If you wanted the Nodes type to be able hold a reference to Self (to avoid the clone() ), then Nodes would need to be generic with a lifetime parameter. 如果您希望Nodes类型能够保存对Self的引用(以避免使用clone() ), 那么 Nodes需要具有生命周期参数的通用性。 That's not the only way to avoid the clone() , though: you could use Rc . 但这并不是clone()的唯一方法:你可以使用Rc

As the answer by Anders Kaseorg already explains: you might not need GATs here, if you can live with cloning your Vec containing the vertices. 正如Anders Kaseorg的回答已经解释过:如果你可以克隆包含顶点的Vec ,你可能不需要GAT However, that's probably not what you want. 但是,这可能不是你想要的。 Instead, you usually want to have an iterator that references the original data. 相反,您通常希望拥有一个引用原始数据的迭代器。

To achieve that, you in fact ideally want to use GATs. 为实现这一目标,您实际上理想地想要使用GAT。 But since they are not part of the language yet, let's tackle your main question: Is there any way to simulate Generic Associated Types? 但由于它们还不是语言的一部分,让我们解决你的主要问题: 有没有办法模拟通用关联类型? I actually wrote a very extensive blog post about this topic: “Solving the Generalized Streaming Iterator Problem without GATs” . 我实际上写了一篇关于这个主题的非常广泛的博客文章: “解决没有GAT的广义流式迭代器问题”

Summarizing the article: 总结文章:

  • The easiest way for you is to box the iterator and return it as trait object: 最简单的方法是装入迭代器并将其作为特征对象返回:

     fn nodes(&self) -> Box<dyn Iterator<&'_ V> + '_> 

    As you said, you don't want that, so that's out. 正如你所说,你不希望这样,所以那就是了。

  • You can add a lifetime parameter to your trait and use that lifetime in your associated type and the &self receiver: 您可以在特征中添加一个生命周期参数,并在关联类型和&self接收器中使用该生命周期:

     trait GraphData<'s, V: 's> { type NodesIter: Iterator<Item = &'s V>; fn nodes(&'s self) -> Self::NodesIter; } struct MyGraph<V> { nodes: Vec<V>, } impl<'s, V: 's> GraphData<'s, V> for MyGraph<V> { type NodesIter = std::slice::Iter<'s, V>; fn nodes(&'s self) -> Self::NodesIter { self.nodes.iter() } } 

    This works! 这有效! However, now you have an annoying lifetime parameter in your trait. 但是,现在你的特质中有一个烦人的生命周期参数。 That might be fine (apart from annoyance) in your case, but it can actually be a critical problem in some situations, so this might or might not work for you. 在你的情况下,这可能没什么问题(除了烦恼),但在某些情况下它实际上可能是一个关键问题,所以这可能会或可能不适合你。

  • You can push the lifetime parameter a level deeper by having a helper trait which works as type level function from lifetime to type. 您可以通过使用辅助特征将生命周期参数推向更深的层次,该特征作为从生命周期到类型的类型级别函数。 This makes the situation a little less annoying, because the lifetime parameter is not in your main trait anymore, but it suffers from the same limitation as the prior workaround. 这使得情况不那么烦人,因为生命周期参数不再是您的主要特征,但它受到与先前解决方法相同的限制。

  • You can also go a completely different path and write an iterator wrapper that contains a reference to your graph. 您还可以使用完全不同的路径并编写包含对图形的引用的迭代器包装器。

    This is just a rough sketch, but the basic idea works: your actual inner iterator doesn't contain any reference to the graph (so its type does not need the self lifetime). 这只是一个粗略的草图,但基本思想是有效的:您的实际内部迭代器不包含对图形的任何引用(因此其类型不需要self生命周期)。 The graph reference is instead stored in a specific type Wrap and passed to the inner iterator for each next call. 相反,图形引用存储在特定类型的Wrap并传递给每个next调用的内部迭代器。

    Like this: 像这样:

     trait InnerNodesIter { /* ... */ } struct Wrap<'graph, G: GraphData, I: InnerNodesIter> { graph: &'graph G, iter: I, } type NodesIterInner: InnerNodesIter; fn nodes(&self) -> Wrap<'_, Self, Self::NodesIterInner>; 

    Then you can implement Iterator for Wrap . 然后你可以实现Iterator for Wrap You just need some interface to the inner iterator, which you can pass the reference to the graph. 您只需要一些内部迭代器的接口,您可以将引用传递给图形。 Something like fn next(&mut self, graph: &Graph) -> Option<...> . fn next(&mut self, graph: &Graph) -> Option<...> You need to define the interface in InnerNodesIter . 您需要在InnerNodesIter定义接口。

    This of course suffers from being very verbose. 这当然是非常冗长的。 And it also might be a bit slower, depending on how your iterator works. 它也可能会慢一些,具体取决于迭代器的工作方式。

The short and sad summary is: there is no satisfying workaround that works in every situation. 简短而悲伤的总结是: 没有令人满意的解决方法适用于所有情况。


My opinion in this case: I work on a project where this exact situation occurred multiple times. 在这种情况下我的意见是:我在一个项目中工作,这种情况多次发生。 In my case, I just used the Box solution as it's very easy and works fine. 在我的情况下,我只是使用Box解决方案,因为它非常简单,工作正常。 The only downside is speed (allocation and dynamic dispatch), but the allocation doesn't happen in a tight loop (except if you have a large number of graphs, each with only very few nodes -- unlikely) and the optimizer is probably capable of devirtualizing the dynamic calls in most cases (after all, the real type information is only one function boundary away). 唯一的缺点是速度(分配和动态调度),但分配不会发生在紧密的循环中(除非你有大量的图,每个只有很少的节点 - 不太可能)并且优化器可能是在大多数情况下,虚拟化动态调用(毕竟,实际类型信息只是一个函数边界)。

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

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