使用通用trait作为trait参数

5

我该如何使用相关的泛型类型?以下是我的代码(只有第一行让我感到困扰):

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 是一个泛型特征,适用于 Gameself.data 实现了这种类型的 GameState<Game>。编译器告诉我

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,又使得G需要属于GS

以下是目前的更多定义:

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
        }
    }
}

1
什么是“游戏”? - Brian61354270
“Game”目前是一个空的特征。唯一重要的是,在其扩展函数中,“GameState”获得了正确类型的“Game”。该代码正在用于为棋盘游戏生成蒙特卡罗树。 - Marcel
2个回答

10
impl<G, GS> TreeNode<GS> where G: Game, GS: GameState<G>{
    // ...
}

问题在于G没有限制,所以在这个块中可能会有多个(可能冲突的)实现,因为GS可以为多个G实现GameState<G>。参数G是不明确的。
如果你想让GameState<G>能够为多个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);
            }
        }
    }
}

如果你只想让GameState针对单个G实现,你应该将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);
            }
        }
    }
}

1
第一个选项解决了我的问题,第二个选项提高了我的代码质量!我不知道我可以在特质定义中添加类型。谢谢! - Marcel

5

G的具体类型无法根据TreeNode<GS>的类型来确定;只有在调用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);
            }
        }
    }
}

如果不可能使用不同的G调用expand,那么这是您建模的问题。另一种解决方法是确保对所有TreeNode的类型都已知,例如:
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的限制。 - Marcel
是否有可能在不使用每个语句后面的“where”(并保留特征)的情况下,约束实现中所有函数的实现以适用于特定组合的GGS - Marcel
@Marcel 在我的第一个代码片段中,你可以有两个 Game 的实现,并调用 expand 传递其中之一。 - Peter Hall

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接