2

I am writing a little Cards Against Humanity clone for personal use and am using it as a chance to learn Rust. I currently have the following structs:

// game.rs
pub struct Game<'c> {
    deck: Deck,
    players: Vec<Player<'c>>,
    current_czar_index: usize,
}
// player.rs
pub struct Player<'c> {
    pub hand: Vec<&'c Card>,
    pub max_hand_size: usize,
    pub is_czar: bool,
    pub score: u8,
    pub name: String,
}
// deck.rs
struct DiscardPile {
    pub white: Mutex<Vec<usize>>,
    pub black: Mutex<Vec<usize>>,
}

pub struct Deck {
    white: Vec<Card>,
    black: Vec<Card>,
    pub used_sets: Vec<String>,
    discard_pile: DiscardPile,
}
// card.rs
pub struct Card {
    pub text: String,
    pub uuid: Uuid,
    pub pick: Option<u8>,
}

There are a few others (namely set which is used for reading in JSON files), but they aren't relevant.

I have a function in game.rs

    pub fn deal_hands(&self) -> Result<(), Box<dyn std::error::Error>> {
        for player in &mut self.players {
            while player.hand.len() < player.max_hand_size {
                player.add_card(self.deck.draw_white().unwrap())?;
            }
        }
        Ok(())
    }

that is giving me the following error:

error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
  --> src/game.rs:42:31
   |
42 |                 player.add_card(self.deck.draw_white().unwrap())?;
   |                                           ^^^^^^^^^^
   |

It specifically says that it is expecting &mut Player<'_> but found a lifetime of &mut Player<'c>.

HERE if you want to see the code firsthand.

How do I fix such a lifetime error, or do I need to rethink my architecture?

  • 1
    The answers that you have gotten are unfortunately misleading. Rethinking your architecture is most likely the way to go here. The major red flag is `Game<'c>` where `'c` is the lifetime of *something inside* `Game` (the deck) -- that's a pattern that rarely pans out the way you want in Rust. The linked question has more information. – trent Oct 30 '20 at 15:10
  • In a program like this I don't see a good reason to use references at all -- why shouldn't the `Card`s be moved from the `Deck` to the `Player`s' `hand`s and eventually to the `DiscardPile`? Why do cards get "discarded" as soon as they're drawn? – trent Oct 30 '20 at 15:21
  • That actually seems to be a better plan indeed. Initially I wanted to keep them in the deck so that it would be easier to refill the deck if the cards were exhausted, but your method seems easier in the long run. – Matthew Krohn Oct 31 '20 at 22:55

1 Answers1

0

The architecture seems fine to me. You are probably just missing the passing of a lifetime parameter somewhere.

I'm not totally certain, but I believe issue may be that .draw_white() is returning Option<&Card> and it's not clear what the lifetime of the contained Card should be. I believe it is the case that anytime you return a borrowed value, you must attach a lifetime to the borrowed value. Otherwise, the borrow checker cannot tell how long the borrowed value will live.

Maybe try the following definition for .draw_white() instead

pub fn draw_white<'a>(&self) -> Option<&'a Card> {

You will then need to tweak deal_hands() to be something like the following. (Working on figuring out the right syntax, but I think it's something like this.)

    pub fn deal_hands(&self) -> Result<(), Box<dyn std::error::Error>> {
        for player<'c> in &mut self.players {
            while player.hand.len() < player.max_hand_size {
                player.add_card(self.deck.draw_white<'c>().unwrap())?;
            }
        }
        Ok(())
    }
  • No dice; since there is no parameter to "link" the lifetime of the `Option<&'a Card>` to, it mismatches with the returned reference which has the same lifetime as `self`. – Matthew Krohn Oct 30 '20 at 06:34