Hi there
I have started to learn Rust (and Gtk at the same time), and therefore want to develop a small application.
To my question:
How can I spawn a process::command in the handler "connect_clicked" of a button?
So concretely I try to do the following:
let mut command = process::Command.new("test.sh")
button_a.connect_clicked(move |_| {
let child = command.spawn();
});
button_b.connect_clicked(move |_| {
child.kill().expect("command wasnt running");
});
move || means the closure takes exclusive ownership of variables it uses (child in your case), so two closures owning the same object won't work.
The shared ownership problem is solved with Arc. Arc::new(process::Child…) will give you a value that can be cheaply cloned, so you can have child2 = child.clone() which refers to the same object — the clone just makes another Arc reference, so you can have one for each closure.
If the spawn/kill methods mutate the object, you'll need Arc<Mutex<Child>>, and then use child.lock().unwrap().spawn(). Rust wants to always prevent any race conditions in case both buttons were pressed exactly at the same time. It probably couldn't happen in practice, but rules are rules.
One way you can try doing this is something along the following lines:
enum State {
Init(Option<process::Command>),
Running(process::Child),
}
let cmd: process::Command = ...; // build the cmd
let state = Rc::new(RefCell::new(State::Init(Some(cmd))));
let a_clone = Rc::clone(&state);
button_a.connect_clicked(move |_| {
let state = &mut a_clone.borrow_mut();
let cmd = if let State::Init(cmd) = state {
cmd.take()
} else { panic!() } // or whatever handling strategy here
*state = State::Running(cmd.spawn());
});
let b_clone = Rc::clone(&state);
button_b.connect_clicked(move |_| {
let state = &mut b_clone.borrow_mut();
if let State::Running(child) = state {
child.kill().expect("command wasn't running");
}
});
I'm trying to implement your idea, but encounter another problem. In my code "Child" should be replaced with "Command". That means with one button the command will be spawned, with the other button it will be killed.
With your version I get so far that the command is spawned. But to kill it, I need the child, which is delivered as a return value during spawning. How do I get it into the other button handler?
You can have a variable that is Arc<Mutex<Option<Child>>>>, and initialize it with Arc::new(Mutex::new(None))), then assign a Child there. When killing use .take() on the Option.