【问题标题】:Problems with mutability in a closure闭包中的可变性问题
【发布时间】:2017-06-18 19:31:40
【问题描述】:

我真的不知道如何克服这一点。据我了解,words 被移动到闭包中(这对我来说很好,这是此后唯一要使用的地方)但根据typed_some 需要是 &mut 。错误暗示的想法听起来不错,只是那部分在库中,我不知道他们是否可以实现。
on_edit documentation.

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))
   |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Repo link 如果你想克隆它,一切都会被推送。提交 633ed60 要具体。

【问题讨论】:

  • 我认为您可能需要在闭包中将其作为&amp;mut 借用,如下所示: .on_edit(move | s, input, _| { let words = &mut words; words.typed_some(s, input); }) 我无法测试它(在移动设备上)。
  • @WesleyWiser 谢谢,但我得到了同样的错误,只是稍微改写了bpaste.net/show/dde9cfc1c7fe
  • @WesleyWiser EditView::on_edit 需要带有 Fn 绑定的闭包,这意味着它不能改变其环境(除非通过像 RefCell 这样的逃生舱口,如我的回答所示)。跨度>

标签: rust closures mutable


【解决方案1】:

on_edit 实际上需要一个不可变的回调。不清楚这是开发人员的疏忽还是有意识的决定,但您的代码必须尊重它,让闭包只能访问其封闭环境,不可变。

Rust 确实为这种情况提供了一个逃生口:RefCell type。不要将WordBar 移动到闭包中,而是移动RefCell&lt;WordBar&gt;,然后使用其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 987654332@ 直到关闭返回。

【讨论】:

  • 当我上床睡觉时(当然),我实际上在想“也许这是你必须使用 refcell 的时候?”。谢谢你的解释!
  • @Powersource 与草书开发人员核实on_edit 是否真的需要Fn 或者可以放宽接受FnMut 仍然是一个好主意。如果当前的约束是有充分理由的,用RefCell 欺骗它最终可能会导致运行时出现恐慌。
猜你喜欢
  • 1970-01-01
  • 2012-09-22
  • 1970-01-01
  • 2013-05-19
  • 2014-10-27
  • 1970-01-01
  • 1970-01-01
  • 2015-09-18
  • 1970-01-01
相关资源
最近更新 更多