Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Problems with mutability in a closure

I really don't know how to get past this. As far as I understand it, words is moved into the closure (which is fine by me, it's the only place it's going to be used after this) but needs to be &mut according to typed_some. What the error suggests sounds like a decent idea, it's just that that part is in a library and I don't know if it'd be something they could implement.
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
    }
}

And the errors

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 if you'd rather clone it, everything's pushed. Commit 633ed60 to be specific.

like image 833
Powersource Avatar asked Mar 11 '23 00:03

Powersource


1 Answers

on_edit actually requires an immutable callback. It is not obvious whether that is an oversight or a conscious decision by developers, but your code must respect it by having the closure only access its enclosing environment immutably.

Rust does provide an escape hatch for such situations: the RefCell type. Instead of moving the WordBar into the closure, move a RefCell<WordBar>, and then use its borrow_mut() method to borrow mutably, moving the borrow check to run-time. This compiles:

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

Note that despite bypassing the compile-time borrow check, the above code doesn't give up the guarantees of safe code, it just moves the check into the run-time. RefCell will not allow an already borrowed cell to be borrowed again - if the value is already borrowed, a call to borrow_mut() will panic.

It is up to your code to ensure that this panic is not triggered - in this case by making sure that the actions performed by the closure passed to on_edit don't cause on_edit to be invoked on the same EditView until the closure returns.

like image 157
user4815162342 Avatar answered Mar 17 '23 01:03

user4815162342