New to Rust, really feel like I'm missing something here.
In the below code, there are two nested closures which are important to the question: app.connect_activate
and draw_area.connect_draw
. I am trying to use a channel to send values into the connect_draw
closure, so that what is drawn will reflect the state of a simulation that changes over time (currently represented by random colors).
What I've found is very weird to me and I do not understand at all:
connect_activate
, I can call rx.recv
within connect_draw
(this is the first example below)connect_activate
, I can call rx.recv
within connect_activate
(example omitted for brevity)connect_activate
, I cannot call rx.recv
within connect_draw
; it fails with cannot move out of rx, a captured variable in an Fn closure
(this is the second example below)In other words: I can move the created channel across either closure boundary, but not both. It can move from the scope of main
into the scope of connect_activate
, or from the scope of connect_activate
into the scope of connect_draw
, but it cannot do both in sequence. Why would this be the case? If I can move it into connect_activate
, shouldn't it then be just as "owned" as if it were created there? Both cases do seem to be truly "moved", insofar as they fail if I omit the "move" keyword. What's going on here?
Bonus question: There's a hideous placeholder where I'm re-drawing every time I get any event, because I still don't know how to run events off a timer. Halp?
Example 1: (WORKS)
extern crate cairo;
extern crate rand;
extern crate gtk;
extern crate gdk;
extern crate glib;
use std::{thread, time};
use gtk::prelude::*;
use gtk::{Application, ApplicationWindow, DrawingArea};
use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender};
fn main(){
let app = Application::builder()
.application_id("org.example.HelloWorld")
.build();
app.connect_activate(move |app| {
let (tx, rx ) : (Sender<f64>, Receiver<f64>)= mpsc::channel();
let draw_area = DrawingArea::new();
let _id = draw_area.connect_draw(move |_unused, f| {
let red = rx.recv().unwrap();
let green = rx.recv().unwrap();
let blue = rx.recv().unwrap();
f.set_source_rgb(red,green, blue);
f.paint().expect("Painting failed");
Inhibit(false)
});
let win = ApplicationWindow::builder()
.application(app)
.default_width(320)
.default_height(200)
.title("Hello, World!")
.build();
win.add(&draw_area);
win.show_all();
win.connect_event(|w, _g|{ //Placeholder until I learn to make queue_draw fire on a timer
w.queue_draw();
Inhibit(false)
});
thread::spawn(move || {
loop {
thread::sleep(time::Duration::from_millis(100));
tx.send(rand::random::<f64>()).unwrap();
}
});
});
app.run();
}
Example 2 (DOES NOT WORK)
extern crate cairo;
extern crate rand;
extern crate gtk;
extern crate gdk;
extern crate glib;
use std::{thread, time};
use gtk::prelude::*;
use gtk::{Application, ApplicationWindow, DrawingArea};
use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender};
fn main(){
let app = Application::builder()
.application_id("org.example.HelloWorld")
.build();
let (tx, rx ) : (Sender<f64>, Receiver<f64>)= mpsc::channel();
app.connect_activate(move |app| {
let draw_area = DrawingArea::new();
let _id = draw_area.connect_draw(move |_unused, f| {
let red = rx.recv().unwrap();
let green = rx.recv().unwrap();
let blue = rx.recv().unwrap();
f.set_source_rgb(red,green, blue);
f.paint().expect("Painting failed");
Inhibit(false)
});
let win = ApplicationWindow::builder()
.application(app)
.default_width(320)
.default_height(200)
.title("Hello, World!")
.build();
win.add(&draw_area);
win.show_all();
win.connect_event(|w, _g|{ //Placeholder until I learn to make queue_draw fire on a timer
w.queue_draw();
Inhibit(false)
});
});
thread::spawn(move || {
loop {
thread::sleep(time::Duration::from_millis(100));
tx.send(rand::random::<f64>()).unwrap();
}
});
app.run();
}
A gtk application's connect_activate()
method takes in a Fn
parameter, meaning it can be called multiple times. You cannot move
a captured variable out of a Fn
closure since the variable will have been moved next time you tried to call it. The first example works because it only moves a local variable that would be created each time.