【问题标题】:Rust: Using a generic trait as a trait parameterRust:使用泛型特征作为特征参数
【发布时间】:2020-03-19 22:42:23
【问题描述】:

如何在 Rust 中使用相关的泛型类型?

这是我得到的(只有第一行给我带来了麻烦):

impl<G, GS> TreeNode<GS> where G: Game, GS: GameState<G>{
    pub fn expand(&mut self, game: &G){
        if !self.expanded{
            let child_states = self.data.generate_children(game);
            for state in child_states{
                self.add_child_with_value(state);
            }
        }
    }
}

GameState 是Game 的通用特征,self.data 实现了这种类型的GameState&lt;Game&gt;。编译器告诉我

error[E0207]: the type parameter `G` is not constrained by the impl trait, self type, or predicates
  --> src/mcts.rs:42:6
   |
42 | impl<G, GS> TreeNode<GS> where G: Game, GS: GameState<G>{
   |      ^ unconstrained type parameter

error: aborting due to previous error

但在我看来,我在 expand 函数和 G 需要属于 GS 的事实中都限制了 G。非常感谢任何帮助。

编辑: 以下是目前更多的定义

trait GameState<G: Game>: std::marker::Sized + Debug{
    fn generate_children(&self, game: &G) -> Vec<Self>;
    fn get_initial_state(game: &G) -> Self;
}

trait Game{}

struct TreeNode<S> where S: Sized{
    parent: *mut TreeNode<S>,
    expanded: bool,
    pub children: Vec<TreeNode<S>>,
    pub data: S,
    pub n: u32
}

impl<S> TreeNode<S>{
    pub fn new(data: S) -> Self{
        TreeNode {
            parent: null_mut(),
            expanded: false,
            children: vec![],
            data,
            n: 0
        }
    }

    pub fn add_child(&mut self, mut node: TreeNode<S>){
        node.parent = self;
        self.children.push(node);
    }

    pub fn add_child_with_value(&mut self, val: S){
        let new_node = TreeNode::new(val);
        self.add_child(new_node);
    }

    pub fn parent(&self) -> &Self{
        unsafe{
            &*self.parent
        }
    }

}


【问题讨论】:

  • 什么是Game?
  • Game 当前是一个空特征。唯一重要的是GameState 在其扩展函数中获得了正确类型的Game。该代码用于为棋盘游戏生成蒙特卡洛树。

标签: rust traits


【解决方案1】:
impl<G, GS> TreeNode<GS> where G: Game, GS: GameState<G>{
    // ...
}

问题在于G 不受约束,因此在此块中可能存在多个(可能是冲突的)实现,因为GS 可能为多个G 实现GameState&lt;G&gt;。参数G 不明确。


如果您想让GameState&lt;G&gt; 能够为多个G 实现,您应该将约束从impl 块移到方法中:

// note: G is now a type parameter of the method, not the impl block, which is fine
impl<GS> TreeNode<GS> {
    pub fn expand<G>(&mut self, game: &G) where G: Game, GS: GameState<G> {
        if !self.expanded{
            let child_states = self.data.generate_children(game);
            for state in child_states{
                self.add_child_with_value(state);
            }
        }
    }
}

如果您只想为单个G 实现GameState,则应将G 设为GameState 的关联类型,而不是泛型类型参数:

trait GameState: std::marker::Sized + Debug {
    type G: Game;
    fn generate_children(&self, game: &Self::G) -> Vec<Self>;
    fn get_initial_state(game: &Self::G) -> Self;
}

// note: now G is given by the GameState implementation instead of
//       being a free type parameter
impl<GS> TreeNode<GS> where GS: GameState {
    pub fn expand(&mut self, game: &GS::G){
        if !self.expanded{
            let child_states = self.data.generate_children(game);
            for state in child_states{
                self.add_child_with_value(state);
            }
        }
    }
}

【讨论】:

  • 第一个选项解决了我的问题,第二个选项提高了我的代码质量!我不知道我可以在特征定义中添加一个类型。谢谢!
【解决方案2】:

不能根据TreeNode&lt;GS&gt;的类型确定G的具体类型;只有在调用expand 时才知道。请注意,expand 可以使用 不同 类型为 G 调用两次。

您可以通过约束方法的类型参数而不是整个实现块来表达这一点:

impl<GS> TreeNode<GS> {
    pub fn expand<G>(&mut self, game: &G)
    where
        G: Game,
        GS: GameState<G>,
    {
        if !self.expanded {
            let child_states = self.data.generate_children(game);
            for state in child_states {
                self.add_child_with_value(state);
            }
        }
    }
}

如果不能用不同的Gs 调用expand,那么这是您的建模问题。解决此问题的另一种方法是确保所有TreeNodes 都知道G 的类型。例如:

struct TreeNode<G, S>
where
    S: Sized,
{
    parent: *mut TreeNode<G, S>,
    expanded: bool,
    pub children: Vec<TreeNode<G, S>>,
    pub data: S,
    pub n: u32,
}

然后,一旦考虑了额外的类型参数,您的原始实现块应该可以按照编写的方式工作。

【讨论】:

  • 很好的回应,谢谢!你说expand 可以用不同的类型为G 调用两次是什么意思?在我看来,它仍然像 G 在我的代码中受 GS 约束。
  • 是否可以限制实现中所有函数的实现,使其适用于 G 和 GS 的特定组合,而不在每个语句后使用 where(并将特征保留为是)?
  • @Marcel 在我的第一个代码 sn-p 中,您可以有两个 Game 的实现并调用 expand 传递其中一个。
猜你喜欢
  • 1970-01-01
  • 2020-12-31
  • 1970-01-01
  • 2023-01-12
  • 2020-12-24
  • 2022-10-05
  • 2022-11-23
  • 2011-06-25
  • 1970-01-01
相关资源
最近更新 更多