Compare and contrast traits vs structs with function members

For the sake of this discussion assume we are implementing a game of tic-tac-toe.

We want to abstract the concept of getting a move from a player. We want to be able to inject different specific implementations of this abstract ability into our code.

To this end, I have come up with the following 2 options

struct ControllerStruct<'a> {
    circle_mover: &'a dyn Fn(&GameBoard) -> CellLocation,
    cross_mover: &'a dyn Fn(&GameBoard) -> CellLocation
}

trait ControllerTrait {
    fn circle_mover(gameboard: &GameBoard) -> CellLocation;
    fn cross_mover(gameboard: &GameBoard) -> CellLocation;
}

It seems to me that either of these options can satisfy our abstraction goals. What are the implications of choosing one over the other? Is it purely a matter of style or is there potentially some performance benefit of the struct option? Imagine we want to abstract something that gets called a lot in a tight loop so that small performance gains are worth considering. Is the struct faster because it does not involve dynamic dispatch for example?

I have a feeling that most people would prefer the trait option because it is simpler (does not need a lifetime specifier) but I am interested in any other considerations.

Short update, apparently to define a trait I need to include a reference to self even if I don't want to depend on self

pub trait ControllerTrait {
    fn circle_mover(&self, gameboard: &GameBoard) -> CellLocation;
    fn cross_mover(&self, gameboard: &GameBoard) -> CellLocation;
}

my code would not compile without this.

EDIT: another update

if I define a trait without self, and a function that looks like

fn play_one_move<T: ControllerTrait>(ctrl: T, playing_state: PlayingGameState) -> GameState {...}

Then I can invoke it like T::cross_mover(...)

I tend to feel that if you can you generally want to stick with the trait variation, except in relatively rare cases where you may want to add something like a HRTB (higher-ranked trait bounds) to the dyn Fn, which isn't available with the plain trait member functions without jumping through some hoops.

(just saw that you already figured this out yourself, nevermind.)

No you don't, this compiles perfectly fine:

trait Foo {
   fn foo();
}

maybe you're using the wrong syntax to call the trait method? it should be Controller::circle_mover(...) or <Controller as ControllerTrait>::circle_mover(...)

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.