Rust way to do this?

Went to naively translate something from Python to Rust and got bit by a double linked object relationship. Without boxing, what would be the proper way to (re)do this in Rust?

from typing import List


class Game:
    def __init__(self) -> None:
        self.board: List[int] = []
        self.player = Player(self)

    def player_callback(self) -> None:
        # do something
        pass


class Player:
    def __init__(self, game: Game) -> None:
        self.score: int = 0
        self.game: Game = game

    def play(self) -> None:
        # do something
        # then call to notify the game
        self.game.player_callback()

The Rust way to do that is to not do that in the first place.

On a surface level, I'd change it so that you're supposed to call play on Game, which will then call any relevant methods on the Player. I'd also remove the Player.game field entirely. If there's some logic in Player::play that requires access to something in Game, then you can either pass that information in as a parameter, or move that code to live on Game directly.

In Rust, you want everything to be structured in a strictly acyclic graph, because Rust really, really hates cycles. There are ways you can have reference cycles, but they introduce other problems, and you're generally better off just avoiding them in the first place.

12 Likes

I'd like to add that you can store player in Rc or Arc if you need multiple structs sharing ownership of the Player. However, if you try storing Rcs in circular manner, you can cause memory leaks if you're not careful.

However, the Player shouldn't own the Game it's a part of, so the answer from @DanielKeep is correct. It's hard to tell what your game is doing with Player::play from the snippet you provided, but it looks like the code that calls Player::play should call Game::player_callback right after, instead of that code being included in the play function.

If you call Player::play from multiple places, do something like:

impl Game {
  pub fn play_turn(&mut self) {
    self.player.play()
    // content of player_callback here
  }
}

That way, Player doesn't have to "call back" to Game in order to notify it because the Player (owned by Game) is notified though the struct that owns it.

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.