Understanding Generics Error

pub enum NodeTransition<'a, G: Game, N: Node<'a, G>> {
    Edge(&'a N),
    Terminal(Outcome),
    Undefined
}

error[E0392]: parameter `G` is never used
  --> src/game_tree.rs:15:32
   |
15 | pub(crate) struct GameTree<'a, G: Game, N: Node<'a, G>> {
   |                                ^ unused parameter
   |
   = help: consider removing `G`, referring to it in a field, or using a marker such as `PhantomData`

I don't understand this error. G is not used inside of NodeTransition but I need to pass through to the Node declaration. How do I fix this?

You can likely remove both G and the Node bound.

That probably sound backwards, but adding bounds on the type means that every method and every trait implementation it has need to be restricted by those traits. N will need to implement Node for it to be cloned, printed, sorted, have its equality checked, and so on. Maybe that's not too bad in this case, but it can quickly become an unnecessary limitation.

Secondly, by having the G parameter, you make NodeTransition<'a, X, MyNode> different from NodeTransition<'a, Y, MyNode>, even if MyNode is allowed to implement both Node<X>and Node<Y>. Maybe that's not relevant in this case, but it could be an unnecessary distinction.

The error message hints at a type called PhantomData. It allows you to pretend to store a type without actually doing it. It's a possible option of you want to "brand" your type to prevent mixups.

pub(crate) struct GameTree<'a, N: Node<'a>> {
    nodes: Vec<N>,
    root: &'a N,
}

error[E0107]: trait takes 1 generic argument but 0 generic arguments were supplied
  --> src/game_tree.rs:15:35
   |
15 | pub(crate) struct GameTree<'a, N: Node<'a>> {
   |                                   ^^^^ expected 1 generic argument
   |
note: trait defined here, with 1 generic parameter: `G`
  --> src/game_tree.rs:58:11
   |
58 | pub trait Node<'a, G: Game> {
   |           ^^^^     -
help: add missing generic argument
   |
15 | pub(crate) struct GameTree<'a, N: Node<'a, G>> {

I just need to be able to store a parametric type inside of this struct. It gets annoyed if I just put G without separately declaring G: Game

From this example, it doesn't look like you need the Node bound in GameTree either. Neither nodes nor root makes use of it. It's generally better to only have the bounds on impl blocks or methods that make use of them. That way, you avoid their contagious effect. I.e. that everything that uses the types in a generic way need to repeat their bounds.

The generic G is used for some of the return types of the functions inside of the node

pub trait Node<'a, G: Game> {
    fn new(public_information: G::PublicInformation, transition_map: HashMap<(StateId, StateId, ActionId), NodeTransition<'a, G, Self>>) -> Self;
    fn empty(public_information: G::PublicInformation) -> Box<Self> { Self::new(public_information, HashMap::new()) }
    fn leaf(&self) -> bool;
    fn player(&self) -> G::PlayerId { self.public_state().player() }
    fn public_state(&self) -> &G::PublicInformation;
    fn transition(&self, state_1: StateId, state_2: StateId, action_id: ActionId) -> (StateId, &NodeTransition<'a, G, Self>);
    fn children(&self) -> HashSet<NodeTransition<'a, G, Self>>;
    fn visit(&mut self, active_state: StateId, other_state: StateId, action_id: ActionId, new_state: G) -> Option<Self>;
    fn game(&self) -> G;
}

That's most likely fine and the Game trait doesn't require more type parameters by itself. What I mean is that this could work just as well:

pub(crate) struct GameTree<'a, N> {
    nodes: Vec<N>,
    root: &'a N,
}

pub enum NodeTransition<'a, N> {
    Edge(&'a N),
    Terminal(Outcome),
    Undefined
}

pub trait Node<'a, G: Game> {
    fn new(public_information: G::PublicInformation, transition_map: HashMap<(StateId, StateId, ActionId), NodeTransition<'a, Self>>) -> Self;
    fn empty(public_information: G::PublicInformation) -> Box<Self> { Self::new(public_information, HashMap::new()) }
    fn leaf(&self) -> bool;
    fn player(&self) -> G::PlayerId { self.public_state().player() }
    fn public_state(&self) -> &G::PublicInformation;
    fn transition(&self, state_1: StateId, state_2: StateId, action_id: ActionId) -> (StateId, &NodeTransition<'a, Self>);
    fn children(&self) -> HashSet<NodeTransition<'a, Self>>;
    fn visit(&mut self, active_state: StateId, other_state: StateId, action_id: ActionId, new_state: G) -> Option<Self>;
    fn game(&self) -> G;
}

1 Like

To explain a bit further, when you have a type parameter N, without any explicit trait bounds, any type that has a size known at comple time can be used there. Including anything that implements Node<G>. This means you are already good to go for just storing a value as it is. Both GameTree and NodeTransition will be fine without any specific bounds because all they do are store values as they are, or behind a reference. This is similar to the definition of Vec<T>, which also doesn't need any specific trait bounds on its item type parameter.

Later on, when you have some impl that need N to act as a node, you can require N: Node<G> there. Then that requirement is limited to the place where it's actually used and only types that fulfill it will be usable with those methods.

Thank you for the detailed help, this makes a lot of sense