【问题标题】:In Rust, how can I create a function which will accept a "Marker Component" as a type parameter?在 Rust 中,如何创建一个接受“标记组件”作为类型参数的函数?
【发布时间】:2021-10-30 18:47:51
【问题描述】:

我目前正在查看Rust Sokoban 教程,在我输入代码时玩弄代码,看看如何在不破坏它的情况下“改进”它。在Pushing boxes 的章节中,他们引入了两个“标记组件”来“告诉我们哪些实体是可移动的,哪些是不可移动的”:

#[derive(Component, Default)]
#[storage(NullStorage)]
pub struct Movable;

#[derive(Component, Default)]
#[storage(NullStorage)]
pub struct Immovable;

后来,我们有如下代码:

                let mut mov: HashMap<(u8, u8), Index> = (&entities, &movables, &positions)
                    .join()
                    .map(|t| ((t.2.x, t.2.y), t.0.id()))
                    .collect::<HashMap<_, _>>();
                let mut immov: HashMap<(u8, u8), Index> = (&entities, &immovables, &positions)
                    .join()
                    .map(|t| ((t.2.x, t.2.y), t.0.id()))
                    .collect::<HashMap<_, _>>();

其中&amp;entitiesEntities 的实例,&amp;positionsWriteStorage&lt;Position&gt; 的实例,&amp;movables&amp;immovables 分别是ReadStorage&lt;'a, Movable&gt;ReadStorage&lt;'a, Immovable&gt; 的实例。

作为一个对 DRY 代码神经过敏的人,上面的两个函数真的让我很恼火,我想重构它,但我还没有弄清楚如何编写一个可以处理不同的函数&amp;movables&amp;immovables 的类型。

例如,如果我尝试这个功能:

fn collect<T>(entities: &Entities, storable: &ReadStorage<T>, positions: &WriteStorage<Position>)
    -> HashMap<(u8, u8), Index> {
    (&entities, &storable, &positions)
            .join()
            .map(|t| ((t.2.x, t.2.y), t.0.id()))
            .collect::<HashMap<_, _>>()
}

然后像这样调用它:

let mov: HashMap&lt;(u8, u8), Index&gt; = collect(&amp;entities, &amp;movables, &amp;positions);

或喜欢:

let mov: HashMap&lt;(u8, u8), Index&gt; = collect::&lt;Movable&gt;(&amp;entities, &amp;movables, &amp;positions);

...编译失败:

错误[E0277]:特征绑定T: specs::Component不满足
--> src\resources\input_system.rs:95:46 | 95 | fn 收集(实体:&Entities,可存储:&ReadStorage,位置:&WriteStorage) | ^^^^^^^^^^^^^^^ 特征 specs::Component 没有为 T 实现 | ::: C:\Users\BrianKessler.cargo\registry\src\github.com-1ecc6299db9ec823\specs-0.17.0\src\storage\mod.rs:143:29 | 143 | pub struct MaskedStorage { | --------- MaskedStorage 中的此绑定要求 |求助:考虑限制类型参数T | 95 | fn collect(实体:&Entities,可存储:&ReadStorage,位置:&WriteStorage) | ^^^^^^^^^^^^^^^^^^

错误:由于先前的错误而中止

有关此错误的更多信息,请尝试rustc --explain E0277。 错误:无法编译rust-sokoban

要了解更多信息,请使用 --verbose 再次运行该命令。

例如,如果我尝试这个功能(按照@AlexeyLarionov 的建议):

fn collect<T: specs::Component>(entities: &Entities, storable: &ReadStorage<T>, positions: &WriteStorage<Position>)
    -> HashMap<(u8, u8), Index> {
    (&entities, &storable, &positions)
            .join()
            .map(|t| ((t.2.x, t.2.y), t.0.id()))
            .collect::<HashMap<_, _>>()
}

编译失败:

错误[E0599]: 元组(&amp;&amp;specs::Read&lt;'_, EntitiesRes&gt;, &amp;&amp;specs::Storage&lt;'_, T, Fetch&lt;'_, MaskedStorage&lt;T&gt;&gt;&gt;, &amp;&amp; specs::Storage&lt;'_, Position, FetchMut&lt;'_, MaskedStorage&lt;Position&gt;&gt;&gt;) 存在方法join, 但它的特征界限不满足 --> src\resources\input_system.rs:94:14 | 94 | .join() | ^^^^ 方法无法在 (&amp;&amp;specs::Read&lt;'_, EntitiesRes&gt;, &amp;&amp;specs::Storage&lt;'_, T, Fetch&lt;'_, MaskedStorage&lt;T&gt;&gt;&gt;, &amp; &amp;specs::Storage&lt;'_, Position, FetchMut&lt;'_, MaskedStorage&lt;Position&gt;&gt;&gt;) 上调用 由于不满意的特征界限| =注意:以下特征 边界不满足: &amp;&amp;specs::Read&lt;'_, EntitiesRes&gt;: specs::Join 这是(&amp;&amp;specs::Read&lt;'_, EntitiesRes&gt;, &amp;&amp;specs::Storage&lt;'_, T, Fetch&lt;'_, MaskedStorage&lt;T&gt;&gt;&gt;, &amp;&amp;specs::Storage&lt;'_ , Position, FetchMut&lt;'_, MaskedStorage&lt;Position&gt;&gt;&gt;): specs::Join 所要求的 &amp;&amp;specs::Storage&lt;'_, T, Fetch&lt;'_, MaskedStorage&lt;T&gt;&gt;&gt;: specs::Join 这是(&amp;&amp;specs::Read&lt;'_, EntitiesRes&gt;, &amp;&amp;specs::Storage&lt;'_, T, Fetch&lt;'_, MaskedStorage&lt;T&gt;&gt;&gt;, &amp;&amp;specs::Storage&lt;'_ , Position, FetchMut&lt;'_, MaskedStorage&lt;Position&gt;&gt;&gt;): specs::Join 所要求的 &amp;&amp;specs::Storage&lt;'_, Position, FetchMut&lt;'_, MaskedStorage&lt;Position&gt;&gt;&gt;: specs::Join 这是(&amp;&amp;specs::Read&lt;'_, EntitiesRes&gt;, &amp;&amp;specs::Storage&lt;'_, T, Fetch&lt;'_, MaskedStorage&lt;T&gt;&gt;&gt;, &amp;&amp;specs::Storage&lt;'_ , Position, FetchMut&lt;'_, MaskedStorage&lt;Position&gt;&gt;&gt;): specs::Join所要求的

错误:由于先前的错误而中止

有关此错误的更多信息,请尝试rustc --explain E0599。 错误:无法编译rust-sokoban

要了解更多信息,请使用 --verbose 再次运行该命令。

我真的需要引入一个特征来完成这项工作吗? 如果是这样,该特征应该是什么样的? 我还需要做哪些其他更改? 我是否需要增加如此多的复杂性以至于治疗变得比疾病更糟?

【问题讨论】:

  • 愚蠢的问题,但是您是否尝试按照编译器的优雅建议使用fn collect&lt;T: specs::Component&gt; 声明您的函数?
  • @AlexeyLarionov,这不是一个愚蠢的问题。我是 Rust 的新手,没有将编译器的反馈解释为我应该这样做的建议。我会试试看。谢谢。 :-)
  • 您似乎通过双重引用获取了所有三个对象,为什么呢?我不熟悉 specs::Join 特征,但从 docs 看来它是为对象和单个引用实现的。尝试像 (entities, storable, positions).join()...blahblah 这样传递对象,消除双重引用
  • 在您的原始代码中,您将它们作为单一引用,
  • 虽然这通常不是问题,因为即使你有一个像&amp;&amp;&amp;&amp;&amp;&amp;&amp;Vec&lt;_&gt; 这样有多个引用级别的对象,你仍然可以很好地调用它的方法,类似@987654362 @ 可以很容易地转换为(A, B),而(A, &amp;B) 不能转换为(A, B),除非你明确定义了一些转换

标签: generics rust functional-programming traits dry


【解决方案1】:

正如我们在 cmets 中所讨论的,有两个错误来源:

  1. ReadStorage&lt;T&gt;WriteStorage&lt;T&gt; 上运行的方法要求T 成为Component,幸运的是MovableImmovable 已经是,所以要修复它,我们可以简单地限制T特征。函数的声明看起来像这样fn collect&lt;T: specs::Component&gt; (...)
  2. 由于复制粘贴,在对象(&amp;entities, &amp;storable, &amp;positions) 上调用了.join() 方法,其中entitiesstorablepositions 是已在函数声明中指定的引用,因此调用了.join() (简单地说)类型为(&amp;&amp;A, &amp;&amp;B, &amp;&amp;C),而它是为(&amp;A, &amp;B, &amp;C) 定义的。要解决这个问题 我们需要在collect 函数中调用(entities, storable, positions).join()

代码的最终版本如下所示:

fn collect<T: specs::Component>(entities: &Entities, storable: &ReadStorage<T>, positions: &WriteStorage<Position>)
    -> HashMap<(u8, u8), Index> {
    (entities, storable, positions)
            .join()
            .map(|t| ((t.2.x, t.2.y), t.0.id()))
            .collect::<HashMap<_, _>>()
}

【讨论】:

  • 有点切题,但多级引用是否经常(或曾经)有用,或者这通常是需要注意的代码异味?
  • 几乎没有。有时多级可变引用很有用。并且迭代器链函数有时会产生多级引用作为副作用。
猜你喜欢
  • 1970-01-01
  • 2011-08-27
  • 2013-02-12
  • 1970-01-01
  • 2013-07-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多