包含一条数据和被引用数据的结构体中的生命周期

Lifetimes in Structs that Contain both a piece of data and the data being referenced

我正在编写一个小的 Cards Against Humanity 克隆供个人使用,并将其作为学习 Rust 的机会。我目前有以下结构:

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

还有其他一些(即用于读取 JSON 文件的集合),但它们不相关。

我在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(())
    }

出现以下错误:

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())?;
   |                                           ^^^^^^^^^^
   |

它明确表示它期待 &mut Player<'_> 但发现生命周期为 &mut Player<'c>

HERE 如果您想直接查看代码。

如何修复此类生命周期错误,或者我是否需要重新考虑我的架构?

我觉得这个架构不错。您可能只是在某处错过了生命周期参数的传递。

我不完全确定,但我认为问题可能是 .draw_white() 正在 returning Option<&Card> 并且不清楚所包含的 Card 的生命周期是多少] 应该。我相信这种情况是,任何时候你 return 一个借来的价值,你都必须为借来的价值附加一个生命周期。否则,借用检查器无法判断借入值的有效期。

也许可以试试下面的 .draw_white() 定义

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

然后您需要将 deal_hands() 调整为如下所示。 (正在找出正确的语法,但我认为它是这样的。)

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

我想我已经解决了。幸运的是,在克服了一些错误之后,一切都构建得很好。

原始错误

好的。这是我在尝试构建您的箱子时看到的完整错误。

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())?;
   |                                           ^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 39:2...
  --> src/game.rs:39:2
   |
39 |     pub fn deal_hands(&self) -> Result<(), Box<dyn std::error::Error>> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...so that reference does not outlive borrowed content
  --> src/game.rs:42:21
   |
42 |                 player.add_card(self.deck.draw_white().unwrap())?;
   |                                 ^^^^^^^^^
note: but, the lifetime must be valid for the lifetime `'c` as defined on the impl at 20:6...
  --> src/game.rs:20:6
   |
20 | impl<'c> Game<'c> {
   |      ^^
note: ...so that the types are compatible
  --> src/game.rs:42:12
   |
42 |                 player.add_card(self.deck.draw_white().unwrap())?;
   |                        ^^^^^^^^
   = note: expected `&mut Player<'_>`
              found `&mut Player<'c>`

看起来问题实际上出在 deal_hands() 上。您需要将生命周期指定为 &'c mut self,而不是让 &self 的生命周期为 elided。 (mut 是因为你正在变异 self.players。)我不完全确定为什么 Rust 没有将 self 的生命周期省略为 'c,但这可能是另一个原因主题。

下一个错误

我 运行 遇到的下一个错误是

error: captured variable cannot escape `FnMut` closure body                                          
   --> src/game.rs:106:17                                                                            
    |                                                                                                
106 |                     .map(|num| player.play_card_by_index(num as usize))                        
    |                              - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returns a reference to a
 captured variable which escapes the closure body                                                    
    |                              |                                                                 
    |                              inferred to be a `FnMut` closure                                  
    |                                                                                                
    = note: `FnMut` closures only have access to their captured variables while they are executing...
    = note: ...therefore, they cannot allow references to captured variables to escape

对于这一个,问题实际上不在错误消息中。您需要在 .play_card_by_index() 的 return 值中指定 Card 的生命周期,因为否则不清楚对 Card 的引用实际上是否超过闭包。这意味着您需要将 .play_card_by_index() 调整为 return &'c Card' instead of &Card`。

最终错误

您的 .get_player_names() 中出现了一个小错误。您只需要使用 .iter() 而不是 .into_iter(),因为前者不会取得 self 的所有权。另外,出于类似的原因,您可能也想 .clone() 播放器名称。

完全差异

这是完整的差异

diff --git a/src/game.rs b/src/game.rs
index 573d09d..5ccbf6b 100644
--- a/src/game.rs
+++ b/src/game.rs
@@ -36,7 +36,7 @@ impl<'c> Game<'c> {
 
        /// Hands need to be filled on every turn, so this lets us fill everyones'
        /// hands at once.
-       pub fn deal_hands(&self) -> Result<(), Box<dyn std::error::Error>> {
+       pub fn deal_hands(&'c mut 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())?;
@@ -124,7 +124,10 @@ impl<'c> Game<'c> {
        }
 
        pub fn get_player_names(&self) -> Vec<String> {
-               self.players.into_iter().map(|player| player.name).collect()
+               self.players
+                       .iter()
+                       .map(|player| player.name.clone())
+                       .collect()
        }
 }
 
diff --git a/src/player.rs b/src/player.rs
index 4bd6848..9f95373 100644
--- a/src/player.rs
+++ b/src/player.rs
@@ -46,7 +46,7 @@ impl<'c> Player<'c> {
 
        /// Used in the TUI, we can play a card by index and get the reference
        /// to the card we played
-       pub fn play_card_by_index(&mut self, index: usize) -> & Card {
+       pub fn play_card_by_index(&mut self, index: usize) -> &'c Card {
                self.hand.remove(index)
        }
 
@@ -56,7 +56,7 @@ impl<'c> Player<'c> {
        /// instead of making the card on the client and sending the full card info,
        /// we can just send the UUID[s] of the cards that were played with some
        /// data about the order they were played in.
-       pub fn play_card_by_uuid(&mut self, uuid: Uuid) -> Option<& Card> {
+       pub fn play_card_by_uuid(&mut self, uuid: Uuid) -> Option<&Card> {
                // TODO: Find a better way to do this
                let mut index_of_card: usize = self.hand.len() + 1;
                for (index, card) in self.hand.iter().enumerate() {

祝你好运!

这看起来是个有趣的项目。我希望你继续用 Rust 构建它!