闭包中的可变性问题

时间:2017-02-01 21:33:26

标签: rust closures mutable

我真的不知道如何克服这个问题。据我所知,words被移到了封闭处(这对我来说很好,它是在此之后唯一可以使用的地方),但需要&根据{{​​1}}的mut。错误表明听起来像是一个体面的想法,只是那个部分在图书馆中,我不知道它是否是他们可以实现的东西。
on_edit documentation.

typed_some

错误

extern crate cursive;
extern crate rand;

use cursive::Cursive;
use cursive::views::{Dialog, TextView, EditView, LinearLayout};
use cursive::traits::Identifiable;
use rand::Rng;

fn main() {
    // This really messes with stdout. Seems to disable it by default but when
    // siv is running println prints in random places on the screen.
    let mut siv = Cursive::new();
    siv.add_global_callback('q', |s| s.quit());

    let mut words = WordBar::new();

    siv.add_layer(Dialog::around(LinearLayout::vertical()
            .child(TextView::new(words.update_and_get_bar()).with_id("target_field"))
            .child(EditView::new()
                .on_edit(move |s, input, _| words.typed_some(s, input))
                .with_id("input_field")))
        .title("Keyurses")
        .button("Quit", |s| s.quit()));

    siv.run();
}


type WordList = Vec<&'static str>;

#[derive(Debug)]
struct WordBar {
    words: WordList,
    target_list: WordList,
}

impl WordBar {
    fn new() -> Self {
        WordBar {
            words: include_str!("google-10000-english-usa.txt").lines().collect(),
            target_list: vec!["foo"],
        }
    }

    fn typed_some(&mut self, siv: &mut Cursive, input: &str) {
        // See https://github.com/gyscos/Cursive/issues/102
        // for discussion on this mess

        let mut reset_input = false;
        {
            let target_word = siv.find_id::<TextView>("target_field").unwrap();
            if target_word.get_content() == input {
                target_word.set_content(self.update_and_get_bar());
                reset_input = true;
            }
        }
        if reset_input {
            siv.find_id::<EditView>("input_field").unwrap().set_content("");
        }
    }

    fn rand_word(&self) -> &'static str {
        let mut rng = rand::thread_rng();
        rng.choose(&self.words).unwrap()
    }

    fn update_and_get_bar(&mut self) -> String {
        if self.target_list.len() > 0 {
            self.target_list.pop();
        }
        while self.target_list.len() < 5 {
            let new_word = self.rand_word();
            self.target_list.push(new_word);
        }
        let mut bar_text: String = "".to_string();
        for word in &self.target_list {
            if bar_text == "" {
                bar_text = word.to_string();
            } else {
                bar_text.push_str(" ");
                bar_text.push_str(word);
            }
        }
        bar_text
    }
}

Repo link如果您想克隆它,那么一切都会被推动。承诺633ed60具体。

1 个答案:

答案 0 :(得分:6)

on_edit实际上需要一个不可变的回调。这是否是开发人员的疏忽或有意识的决定并不明显,但是您的代码必须通过使闭包只能不可变地访问其封闭环境来尊重它。

Rust确实为这种情况提供了逃生舱:RefCell type。不是将WordBar移动到闭包中,而是移动RefCell<WordBar>,然后使用其borrow_mut()方法可变地借用,将借用检查移动到运行时。这编译:

fn main() {
    let mut siv = Cursive::new();
    siv.add_global_callback('q', |s| s.quit());

    let words = ::std::cell::RefCell::new(WordBar::new());

    let text = words.borrow_mut().update_and_get_bar();
    siv.add_layer(Dialog::around(LinearLayout::vertical()
                                 .child(TextView::new(text)
                                        .with_id("target_field"))
                                 .child(EditView::new()
                                        .on_edit(move |s, input, _|
                                                 words.borrow_mut().typed_some(s, input))
                                        .with_id("input_field")))
                  .title("Keyurses")
                  .button("Quit", |s| s.quit()));

    siv.run();
}

请注意,尽管绕过了编译时借用检查,上面的代码并没有放弃安全代码的保证,它只是将检查移动到运行时。 RefCell将不允许再次借用已借用的单元格 - 如果该值已被借用,则对borrow_mut()的调用将会出现恐慌。

由您的代码来确保不会触发此恐慌 - 在这种情况下,通过确保由on_edit传递给on_edit的闭包执行的操作不会导致EditView在关闭返回之前,在相同的{{1}}上调用。