1

Answer

Deleting import use std::ops::BorrowMut fixed my problem

The Rust Analyzer VS-Code extension automatically imported std::ops::BorrowMut at some point during my many trials at figuring this out. This completely stopped me from using the borrow_mut() of RefCell, and following any tutorials on the subject. This was pointed out by a user in the comments, but never posted as an official answer. I selected the earliest answer that demonstrated RefCell could indeed function as I expected and there was something wrong with my setup.

Original Question

I really would like to have a representation of my app state and be able to pause the TUI library cursive to call other command line programs and then resume.

The callbacks for cursive's add_global_callback are static lifetimes (not sure if they are declared this way, or if cursive is just representing how rust makes callbacks static).

I can't seem to use any combination of Rc, RefCell, RwLock, Mutex, Arc, Box, and Weak that I can think of to pass in a reference to app to multiple callbacks. They only way I succeed is if app is copied or cloned and then the state or the original app is not mutated.

The various errors I get are all about the closure outliving the borrow of app or any kind of app_ptr (Rc, Mutex, RefCell) that I create.

closure may outlive the current function, but it borrows *app_ptr, which is owned by the current function may outlive borrowed value *app_ptr

How can I have a single, static, mutable piece of data in Rust?

#[derive(PartialEq, Debug, Clone, Copy)]
enum AppState {
    RUNNING,
    PATCHING,
    QUITTING
}
#[derive(Clone, Copy)]
struct App {
    state: AppState,
}
impl App {
    pub fn stop(&mut self) {
        self.state = AppState::QUITTING;
    }
    pub fn patch(&mut self) {
        self.state = AppState::PATCHING;
    }
    pub fn resume(&mut self) {
        self.state = AppState::RUNNING;
    }
    pub fn is_running(&mut self) -> bool {
        return self.state != AppState::QUITTING;
    }
}
fn main() {
    let mut app = App{state: AppState::RUNNING};
    let mut siv = cursive::default();

    let mut app_ptr = Box::new(app);
    siv.add_global_callback(Key::Esc, |s| {
        app_ptr.stop();
        s.quit();
    });
    siv.add_global_callback(Event::Char('q'), |s| {
        app_ptr.stop();
        s.quit();
    });
    siv.add_global_callback(Event::Char('p'), |s| {
        app_ptr.patch();
        s.quit();
    });

    while app.state != AppState::QUITTING {
        siv.run();
        if app.state == AppState::PATCHING {
            siv.clear();
            let mut child = Command::new("git")
                .arg("add")
                .arg("-p")
                .stdin(std::process::Stdio::inherit())
                .stdout(std::process::Stdio::inherit())
                .spawn()
                .expect("failed to execute child proc");
            match child.wait() {
                Ok(_) => (),
                Err(_) => eprintln!("Could not execute get add patch correctly."),
            }
        }
    }
}

I've tried the Rc::new(RefCell::new(app)) trick that i've seen a few places, but the compiler tells me that type Rc<RefCell> does not have a method stop()

    let mut app = App{state: AppState::RUNNING};
    let mut siv = cursive::default();
    let mut app_ptr = Rc::new(RefCell::new(app));
    {
        let app_ptr_for_esc = Rc::clone(&app_ptr);
        siv.add_global_callback(Key::Esc, move |s| {
            app_ptr_for_esc.borrow_mut().stop();
            s.quit();
        });
    }

no method named stop found for mutable reference &mut Rc<RefCell<App>> in the current scope method not found in &mut Rc<RefCell<App>>

chugadie
  • 2,786
  • 1
  • 24
  • 33
  • Does this answer your question? [Share Arc between closures](https://stackoverflow.com/questions/53045522/share-arc-between-closures) – cafce25 Apr 16 '23 at 15:15
  • 1
    You most likely have `std::ops::BorrowMut` in scope and it's `borrow_mut` gets picked over `RefCell::borrow_mut` either just don't import it, you can just use the syntax sugar `&mut` anyways or explicitly call `RefCell::borrow_mut` on the `Rc>` I know there is a dupe for that too but I can't find it at the moment. – cafce25 Apr 16 '23 at 15:57
  • Thank you, after trying too many solutions, I had a use std::{ borrow::BorrowMut } statement, removing that allowed it to compile, thanks @cafce25 – chugadie Apr 16 '23 at 16:06
  • Don't add answers to the question, add them as an answer instead, but in this case it would be preferable to just close the question as dupe of that other one if anyone could find it. – cafce25 Apr 16 '23 at 16:09
  • 1
    @cafce25 do you mean [unexpected auto deref behavior](https://stackoverflow.com/q/62108325/20665825)? – Jonas Fassbender Apr 16 '23 at 16:18

2 Answers2

2

This solution with lazy_static may be more suitable:

extern crate lazy_static;

use lazy_static::lazy_static;
use std::sync::Mutex;

lazy_static! {
    static ref APP: Mutex<App> = Mutex::new(App {
        state: AppState::RUNNING
    });
}

fn main() {
    let mut siv = cursive::default();

    siv.add_global_callback(Key::Esc, |s| {
        APP.lock().unwrap().stop();
        s.quit();
    });
    siv.add_global_callback(Event::Char('q'), |s| {
        APP.lock().unwrap().stop();
        s.quit();
    });
    siv.add_global_callback(Event::Char('p'), |s| {
        APP.lock().unwrap().patch();
        s.quit();
    });

    while APP.lock().unwrap().state != AppState::QUITTING {
        siv.run();
        if APP.lock().unwrap().state == AppState::PATCHING {
            siv.clear();
            let mut child = Command::new("git")
                .arg("add")
                .arg("-p")
                .stdin(std::process::Stdio::inherit())
                .stdout(std::process::Stdio::inherit())
                .spawn()
                .expect("failed to execute child proc");
            match child.wait() {
                Ok(_) => (),
                Err(_) => eprintln!("Could not execute get add patch correctly."),
            }
        }
    }
}
Kaplan
  • 2,572
  • 13
  • 14
0

I can't seem to use any combination of Rc, RefCell, RwLock, Mutex, Arc, Box, and Weak that I can think of to pass in a reference to app to multiple callbacks. T

I don't know what exactly you've tried, but Rc<RefCell<App>> compiles for me:

let app = Rc::new(RefCell::new(App{state: AppState::RUNNING}));
let mut siv = cursive::default();

let app_for_esc = app.clone();
let app_for_q = app.clone();
let app_for_p = app.clone();

siv.add_global_callback(Key::Esc, move |s| {
    app_for_esc.borrow_mut().stop();
    s.quit();
});
    
siv.add_global_callback(Event::Char('q'), move |s| {
    app_for_q.borrow_mut().stop();
    s.quit();
});
siv.add_global_callback(Event::Char('p'), move |s| {
    app_for_p.borrow_mut().patch();
    s.quit();
});

Rustexplorer.

Jonas Fassbender
  • 2,371
  • 1
  • 3
  • 19