【问题标题】:Trouble with closure lifetimes闭包生命周期的问题
【发布时间】:2018-04-23 01:20:03
【问题描述】:

我真的在为闭包的生命周期而苦苦挣扎。我正在制作一个简单的刽子手游戏,所有连接都玩一个游戏。我正在尝试将闭包传递给将更新游戏然后广播 JSON 的频道,但我遇到了生命周期问题。

extern crate names;
extern crate ws;

#[macro_use]
extern crate json;

use names::{Generator, Name};
use std::collections::HashSet;
use std::sync::mpsc;
use std::thread;
use ws::{listen, CloseCode, Handler, Message, Result, Sender};

type Job = Box<FnMut(&mut Game) + Send>;

#[derive(Debug)]
struct Game {
    word: Vec<String>,
    guesses: HashSet<String>,
    misses: u32,
    progress: Vec<String>,
}

impl Game {
    fn increment_miss(&mut self) {
        self.misses += 1;
    }

    fn update_progress(&mut self, guess: &String) {
        for (i, letter) in self.word.iter().enumerate() {
            if letter == guess {
                self.progress[i] = letter.clone();
            }
        }
    }

    fn status(&self) -> &str {
        if self.misses > 10 {
            "lose"
        } else if self.progress == self.word {
            "win"
        } else {
            "active"
        }
    }
}

struct Server {
    out: Sender,
    tx: std::sync::mpsc::Sender<Job>,
}

impl Handler for Server {
    fn on_message(&mut self, msg: Message) -> Result<()> {
        let string_msg = msg.to_string();

        self.tx
            .send(Box::new(move |mut game: &mut Game| {
                if game.guesses.insert(string_msg.clone()) {
                    check_letter(&mut game, &string_msg);
                };

                let status = game.status();

                let progress = game.progress.clone();
                let guesses = game.guesses.clone().into_iter().collect::<Vec<String>>();
                println!(
                    "guesses: {:?}, progress: {:?}, misses: {}, status: {}",
                    guesses, progress, game.misses, status
                );

                self.out.broadcast(json::stringify(object!{
                    "status"  => "status",
                    "progress" => "progress",
                    "guesses" => "guesses",
                    "misses" => "misses",
                }));
            }))
            .unwrap();

        Ok(())
    }

    fn on_close(&mut self, code: CloseCode, reason: &str) {
        match code {
            CloseCode::Normal => println!("The client is done with the connection."),
            CloseCode::Away => println!("The client is leaving the site."),
            _ => println!("The client encountered an error: {}", reason),
        }
    }
}

fn check_letter(game: &mut Game, guess: &String) {
    if game.word.contains(guess) {
        game.update_progress(guess);
    } else {
        game.increment_miss();
    }
}

fn generate_word() -> Vec<String> {
    let mut generator = Generator::with_naming(Name::Plain);
    generator
        .next()
        .unwrap()
        .split("")
        .map(|c| c.to_string())
        .filter(|s| s != "")
        .collect::<Vec<String>>()
}

fn start_game() -> Game {
    let word = generate_word();

    Game {
        progress: vec!["".to_string(); word.len()],
        word: word,
        guesses: HashSet::new(),
        misses: 0,
    }
}

fn main() {
    let (tx, rx) = mpsc::channel::<Job>();

    thread::spawn(move || {
        let mut game = start_game();
        for mut received in rx {
            received(&mut game)
        }
    });

    listen("127.0.0.1:3000", |out| Server {
        out: out,
        tx: mpsc::Sender::clone(&tx),
    }).unwrap();
}

我收到以下错误:

Compiling hang_man v0.1.0 (file:///Users/smykowski/workspace/rust/hang_man)
error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> src/main.rs:57:22
   |
57 |           self.tx.send(Box::new(move |mut game: &mut Game| {
   |  ______________________^
58 | |             if game.guesses.insert(string_msg.clone()) {
59 | |                 check_letter(&mut game, &string_msg);
60 | |             };
...  |
75 | |             }));
76 | |         })).unwrap();
   | |__________^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 54:5...
  --> src/main.rs:54:5
   |
54 | /     fn on_message(&mut self, msg: Message) -> Result<()> {
55 | |         let string_msg = msg.to_string();
56 | |
57 | |         self.tx.send(Box::new(move |mut game: &mut Game| {
...  |
78 | |         Ok(())
79 | |     }
   | |_____^
note: ...so that the type `[closure@src/main.rs:57:31: 76:10 string_msg:std::string::String, self:&mut Server]` will meet its required lifetime bounds
  --> src/main.rs:57:22
   |
57 |           self.tx.send(Box::new(move |mut game: &mut Game| {
   |  ______________________^
58 | |             if game.guesses.insert(string_msg.clone()) {
59 | |                 check_letter(&mut game, &string_msg);
60 | |             };
...  |
75 | |             }));
76 | |         })).unwrap();
   | |__________^
   = note: but, the lifetime must be valid for the static lifetime...
note: ...so that expression is assignable (expected std::boxed::Box<for<'r> std::ops::FnMut(&'r mut Game) + std::marker::Send + 'static>, found std::boxed::Box<for<'r> std::ops::FnMut(&'r mut Game) + std::marker::Send>)
  --> src/main.rs:57:22
   |
57 |           self.tx.send(Box::new(move |mut game: &mut Game| {
   |  ______________________^
58 | |             if game.guesses.insert(string_msg.clone()) {
59 | |                 check_letter(&mut game, &string_msg);
60 | |             };
...  |
75 | |             }));
76 | |         })).unwrap();
   | |__________^

【问题讨论】:

  • 我在帮助您解决关闭问题时遇到了一些麻烦(我的大脑无法理解今天的生命周期问题......)......所以我决定稍微重写它以使用结构来执行你的工作反而。 You can see my rewrite in the Playground,虽然它不会在那里编译(它在本地编译)。
  • RE 关闭问题 - 他们将默认为 'static 绑定,我无法为您缩小范围并使其编译。我觉得它有可能......我现在不知道怎么做。

标签: rust closures lifetime


【解决方案1】:

这里有些混乱,因为 Rust 对生命周期的某些特性不是很明确。在这个例子中,

  1. 闭包的生命周期受到所有移入其中的参数的生命周期的限制。在您的情况下,由于self.out.broadcast 行,关闭受到self 的限制。请注意,self 在这种情况下实际上是来自fn on_message 中的&amp;self 参数的引用。本质上,您创建了类似的东西

    Box<FnMut(&mut Game) + Send + 'a>
    

    其中'a&amp;self 的生命周期。

  2. 当你创建一个装箱的 trait 对象时,默认的生命周期是 'static。这意味着

    type Job = Box<FnMut(&mut Game) + Send> = Box<FnMut(&mut Game) + Send + 'static>
    

    为避免这种情况,您可以使自我克隆并将self.clone() 移动到此闭包内。

【讨论】:

  • 我的意思是受到移动参数生命周期的限制。 OFC 它的寿命与它的寿命一样长,但这并没有传达任何有关问题的信息)
  • Stack Overflow 上的问题表明,生命周期是 Rust 最令人困惑的方面之一。在试图解释它们时,使用模糊和易混淆的术语不太可能帮助最需要它的人。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-12-10
  • 1970-01-01
  • 2012-12-29
  • 2011-10-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多