闭包中的可变性问题

4

我真的不知道如何处理这个问题。据我所了解,words被移动到闭包中(这对我来说没关系,因为它是在此之后唯一要使用的地方),但根据typed_some的要求需要是&mut类型。错误提示听起来像是一个不错的主意,只是那部分在一个库中,我不知道他们是否能够实现。
on_edit文档。

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

并且出现错误

error: cannot borrow captured outer variable in an `Fn` closure as mutable
  --> src/main.rs:20:45
   |
20 |                 .on_edit(move |s, input, _| words.typed_some(s, input))
   |                                             ^^^^^
   |
help: consider changing this closure to take self by mutable reference
  --> src/main.rs:20:26
   |
20 |                 .on_edit(move |s, input, _| words.typed_some(s, input))
   |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

如果您希望克隆它,这里是代码库链接,所有内容都已经推送完成。具体来说,是提交了633ed60。


我认为你需要在闭包内部使用 &mut 借用,像这样:.on_edit(move | s, input, _| { let words = &mut words; words.typed_some(s, input); })不过我无法测试(在手机上)。 - Wesley Wiser
@WesleyWiser 谢谢,但我得到了相同的错误,只是稍微改了一下措辞 https://bpaste.net/show/dde9cfc1c7fe - Powersource
1
@WesleyWiser EditView::on_edit 需要一个带有 Fn 绑定的闭包,这意味着它不能改变其环境(除非通过像我的答案中所示的逃逸口 RefCell)。 - user4815162342
1个回答

7

on_edit实际上需要一个不可变的回调函数。开发人员是否有意决定这一点并不明显,但您的代码必须通过仅对其封闭环境进行不可变访问来尊重它。

Rust确实为这种情况提供了一个逃生口: RefCell类型。不要将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() 将会引发 panic。

需要您的代码确保不触发此 panic - 在本例中,通过确保由传递给 on_edit 的闭包执行的操作不会导致在闭包返回之前在同一个 EditView 上调用 on_edit


当我上床睡觉时(当然),我真的想到了“也许这就是你需要使用RefCell的时候?”谢谢解释! - Powersource
1
@Powersource,向草书开发人员确认on_edit是否真的需要一个Fn或者它可以放松为接受一个FnMut仍然是一个好主意。如果当前的限制有充分的理由,那么使用RefCell欺骗它最终可能会导致运行时崩溃。 - user4815162342

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接