Cannot move out a captured variable in an `Fn` closure - rust

I'm getting these error messages in my gkt-rs code:
error[E0507]: cannot move out of `image`, a captured variable in an `Fn` closure
error[E0382]: borrow of moved value: `window`
I've read this question and this question, but don't see how to apply it to my problem. I assume the answer involves RefCell, but can't get the invocation right.
This is my code:
use gtk::prelude::*;
use gtk::{Application, ApplicationWindow, Box, Button, FileChooserDialog, Image};
const APP_ID: &str = "org.gtk_rs.test";
fn main() {
let app = Application::builder().application_id(APP_ID).build();
app.connect_activate(build_ui);
app.run();
}
fn build_ui(app: &Application) {
let image = Image::builder().build();
let button = Button::builder().label("Press me!").build();
let mbox = Box::builder().build();
mbox.append(&button);
mbox.append(&image);
let window = ApplicationWindow::builder()
.application(app)
.title("My GTK App")
.child(&mbox)
.build();
button.connect_clicked(move |_| {
let file_chooser = FileChooserDialog::new(
Some("Open image"),
Some(&window),
gtk::FileChooserAction::Open,
&[
("_Cancel", gtk::ResponseType::Cancel),
("_Open", gtk::ResponseType::Accept),
], );
file_chooser.connect_response(move |file_chooser, response| {
if response == gtk::ResponseType::Accept {
let file = file_chooser.file().expect("Couldn't get file");
let filename = file.path().expect("Couldn't get file path");
image.set_from_file(Some(filename));
}
file_chooser.destroy();
});
file_chooser.show();
});
window.present();
}
I'd be grateful for any suggestions.

Thanks #Masklinn, the text_viewer example had the answer:
use glib_macros::clone;
use gtk::prelude::*;
use gtk::{Application, ApplicationWindow, Box, Button, FileChooserDialog, Image};
const APP_ID: &str = "org.gtk_rs.test";
fn main() {
let app = Application::builder().application_id(APP_ID).build();
app.connect_activate(build_ui);
app.run();
}
fn build_ui(app: &Application) {
let image = Image::builder().build();
let button = Button::builder().label("Press me!").build();
let mbox = Box::builder().build();
mbox.append(&button);
mbox.append(&image);
let window = ApplicationWindow::builder()
.application(app)
.title("My GTK App")
.child(&mbox)
.build();
button.connect_clicked(clone!(#weak window => move |_| {
let file_chooser = FileChooserDialog::new(
Some("Open image"),
Some(&window),
gtk::FileChooserAction::Open,
&[
("_Cancel", gtk::ResponseType::Cancel),
("_Open", gtk::ResponseType::Accept),
], );
file_chooser.connect_response(clone!(#weak image => move |file_chooser, response| {
if response == gtk::ResponseType::Accept {
let file = file_chooser.file().expect("Couldn't get file");
let filename = file.path().expect("Couldn't get file path");
image.set_from_file(Some(filename));
}
file_chooser.destroy();
}));
file_chooser.show();
}));
window.present();
}

Related

Sharing variable in two closures

I'm listening to user input in an gtk-rs input element. input.connect_changed triggers when the input changes and input.connect_activate triggers when Enter is pressed.
use gtk::prelude::*;
use gtk::{Application, ApplicationWindow};
use std::process::{Command, Output};
fn main() {
let app = Application::builder()
.application_id("com.jwestall.ui-demo")
.build();
app.connect_activate(build_ui);
app.run();
}
fn run_command(command: &str) -> Output {
Command::new("sh")
.arg("-c")
.arg(command)
.output()
.unwrap_or_else(|_| panic!("failed to execute {}'", command))
}
fn build_ui(app: &Application) {
let input = gtk::Entry::builder()
.placeholder_text("input")
.margin_top(12)
.margin_bottom(12)
.margin_start(12)
.margin_end(12)
.build();
let window = ApplicationWindow::builder()
.application(app)
.title("gtk-app")
.child(&input)
.build();
window.show_all();
input.connect_changed(|entry| {
let input_text = entry.text();
let command = format!("xdotool search --onlyvisible --name {}", input_text);
let window_id_output = run_command(&command);
if window_id_output.status.success() {
println!(
"stdout: {}",
String::from_utf8_lossy(&window_id_output.stdout)
);
} else {
println!(
"sterr: {}",
String::from_utf8_lossy(&window_id_output.stderr)
);
}
});
input.connect_activate(move |entry| {
let input_text = entry.text();
// // `xdotool windowactivate` doesn't produce any output
let command = format!("xdotool windowactivate {}", window_id_output);
let window_activate_output = run_command(&command);
println!("window_activate: {}", window_activate_output);
window.hide();
window.close();
});
}
I want to set window_id_output in input.connect_changed, then use it in input.connect_activate (in the xdotool windowactivate {} command).
How can I use window_id_output this way in these two closures?
Rust Playground
As Sven Marnach said, you can use Rc<RefCell<..>> to move data between closures.
The simplest example is probably this one, probably how the gtk event loop works anyways:
use std::rc::Rc;
use std::cell::RefCell;
fn main() {
let a = Rc::new(RefCell::new(0));
let a_ref = Rc::clone(&a);
let closure_1 = move || {
let mut a = a_ref.borrow_mut();
*a += 1;
println!("closure_1: {}", &a);
};
let a_ref = Rc::clone(&a);
let closure_2 = move || {
let a = a_ref.borrow();
println!("closure_2: {}", &a);
};
for _ in 1..10 {
closure_1();
closure_2();
}
}
For your specific case, see a reduced example below (based on your code):
use std::cell::RefCell;
use std::rc::Rc;
use gtk::prelude::*;
use gtk::{Application, ApplicationWindow};
fn main() {
let app = Application::builder()
.application_id("com.jwestall.ui-demo")
.build();
app.connect_activate(build_ui);
app.run();
}
fn process(s: &str) -> String {
format!("you entered '{}'", s)
}
fn build_ui(app: &Application) {
let input = gtk::Entry::builder()
.placeholder_text("input")
.margin_top(12)
.margin_bottom(12)
.margin_start(12)
.margin_end(12)
.build();
let window = ApplicationWindow::builder()
.application(app)
.title("gtk-app")
.child(&input)
.build();
window.show_all();
let shared_var = Rc::new(RefCell::new(String::new()));
let shared_var_ref = Rc::clone(&shared_var);
input.connect_changed(move |entry| {
let input_text = entry.text();
let mut shared = shared_var_ref.borrow_mut();
*shared = process(&input_text);
});
let shared_var_ref = Rc::clone(&shared_var);
input.connect_activate(move |_entry| {
let shared = shared_var_ref.borrow();
println!("{}", shared);
window.hide();
window.close();
});
}

How to fix "Value moved here, in previous iteration of loop"?

I want to write a program that passes messages from a local websocket to an remote and vice versa, but when I add a while to spawn threads I get an error. How can I fix this?
The exact same error also shows up with ws_local.
error[E0382]: use of moved value: `write_remote`
|
42 | let (mut write_remote, mut read_remote) = ws_remote.split();
| ---------------- move occurs because `write_remote` has type `SplitSink<WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>, Message>`, which does not implement the `Copy` trait
...
70 | let _handle_two = task::spawn(async move {
| ________________________________________________^
71 | | while let Some(msg) = read_local.next().await {
72 | | let msg = msg?;
73 | | if msg.is_text() || msg.is_binary() {
74 | | write_remote.send(msg).await;
| | ------------ use occurs due to use in generator
... |
78 | | Result::<(), tungstenite::Error>::Ok(())
79 | | });
| |_______^ value moved here, in previous iteration of loop
Here is my code:
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
use tokio::net::{TcpListener, TcpStream};
use futures_util::{future, SinkExt, StreamExt, TryStreamExt};
use tokio_tungstenite::{
connect_async,
accept_async,
tungstenite::{Result},
};
use http::Request;
use tokio::sync::oneshot;
use futures::{
future::FutureExt, // for `.fuse()`
pin_mut,
select,
};
use tokio::io::AsyncWriteExt;
use std::io;
use std::net::SocketAddr;
use std::thread;
use tokio::spawn;
use tokio::task;
async fn client() -> Result<()> {
// Client
let request = Request::builder()
.method("GET")
.header("Host", "demo.piesocket.com")
// .header("Origin", "https://example.com/")
.header("Connection", "Upgrade")
.header("Upgrade", "websocket")
.header("Sec-WebSocket-Version", "13")
.header("Sec-WebSocket-Key", tungstenite::handshake::client::generate_key())
.uri("wss://demo.piesocket.com/v3/channel_1?api_key=VCXCEuvhGcBDP7XhiJJUDvR1e1D3eiVjgZ9VRiaV&notify_self")
.body(())?;
let (mut ws_remote, _) = connect_async(request).await?;
let (mut write_remote, mut read_remote) = ws_remote.split();
let listener = TcpListener::bind("127.0.0.1:4444").await.expect("Can't listen");
while let Ok((stream, _)) = listener.accept().await {
let mut ws_local = accept_async(stream).await.expect("Failed to accept");
let (mut write_local, mut read_local) = ws_local.split();
// read_remote.try_filter(|msg| future::ready(msg.is_text() || msg.is_binary()))
// .forward(write_local)
// .await
// .expect("Failed to forward messages");
// read_local.try_filter(|msg| future::ready(msg.is_text() || msg.is_binary()))
// .forward(write_remote)
// .await
// .expect("Failed to forward messages");
let _handle_one = task::spawn(async move {
while let Some(msg) = read_remote.next().await {
let msg = msg?;
if msg.is_text() || msg.is_binary() {
write_local.send(msg).await;
}
};
Result::<(), tungstenite::Error>::Ok(())
});
let _handle_two = task::spawn(async move {
while let Some(msg) = read_local.next().await {
let msg = msg?;
if msg.is_text() || msg.is_binary() {
write_remote.send(msg).await;
}
};
Result::<(), tungstenite::Error>::Ok(())
});
// handle_one.await.expect("The task being joined has panicked");
// handle_two.await.expect("The task being joined has panicked");
}
Ok(())
}
fn main() {
tauri::async_runtime::spawn(client());
tauri::Builder::default()
// .plugin(PluginBuilder::default().build())
.run(tauri::generate_context!())
.expect("failed to run app");
}
looks like you need to make the split streams able to cross threads. The context within the while loop can't access the write_local and write_remote values again and again without a super-context that can hold them.
Here is a working example:
use std::sync::Arc;
use futures_util::{SinkExt, StreamExt};
use http::Request;
use tokio::task;
use tokio::{net::TcpListener, sync::Mutex};
use tokio_tungstenite::{accept_async, connect_async, tungstenite::Result};
async fn client() -> Result<()> {
// Client
let request = Request::builder()
.method("GET")
.header("Host", "demo.piesocket.com")
// .header("Origin", "https://example.com/")
.header("Connection", "Upgrade")
.header("Upgrade", "websocket")
.header("Sec-WebSocket-Version", "13")
.header("Sec-WebSocket-Key", tungstenite::handshake::client::generate_key())
.uri("wss://demo.piesocket.com/v3/channel_1?api_key=VCXCEuvhGcBDP7XhiJJUDvR1e1D3eiVjgZ9VRiaV&notify_self")
.body(())?;
let (ws_remote, _) = connect_async(request).await?;
let (write_remote, read_remote) = ws_remote.split();
let read_remote = Arc::new(Mutex::new(read_remote));
let write_remote = Arc::new(Mutex::new(write_remote));
let listener = TcpListener::bind("127.0.0.1:4444")
.await
.expect("Can't listen");
while let Ok((stream, _)) = listener.accept().await {
let ws_local = accept_async(stream).await.expect("Failed to accept");
let (mut write_local, mut read_local) = ws_local.split();
// read_remote.try_filter(|msg| future::ready(msg.is_text() || msg.is_binary()))
// .forward(write_local)
// .await
// .expect("Failed to forward messages");
// read_local.try_filter(|msg| future::ready(msg.is_text() || msg.is_binary()))
// .forward(write_remote)
// .await
// .expect("Failed to forward messages");
let read_remote = read_remote.clone();
let _handle_one = task::spawn(async move {
let mut read_remote = read_remote.lock_owned().await;
while let Some(msg) = read_remote.next().await {
let msg = msg?;
if msg.is_text() || msg.is_binary() {
write_local.send(msg).await;
}
}
Result::<(), tungstenite::Error>::Ok(())
});
let write_remote = write_remote.clone();
let _handle_two = task::spawn(async move {
let mut write_remote = write_remote.lock_owned().await;
while let Some(msg) = read_local.next().await {
let msg = msg?;
if msg.is_text() || msg.is_binary() {
write_remote.send(msg).await;
}
}
Result::<(), tungstenite::Error>::Ok(())
});
// handle_one.await.expect("The task being joined has panicked");
// handle_two.await.expect("The task being joined has panicked");
}
Ok(())
}
fn main() {
println!("Hello!");
}

How to create other threads in main function

I am using the stream function of redis in actix-web 4, I want to create the consumer in the main function, this is my current code
[dependencies]
actix-web = "4"
tokio = { version = "1", features = ["full"] }
redis = { version = "0.21", features = [
# "cluster",
"tokio-comp",
"tokio-native-tls-comp",
] }
#[actix_web::main]
async fn main() -> std::io::Result<()> {
utils::init::init_envfile();
env_logger::init_from_env(env_logger::Env::new());
let redis_pool = utils::init::init_redis_pool();
let mysql_pool = utils::init::init_mysql_pool();
let redist_stream_consumer = web::block(redis_stream_group);
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(redis_pool.clone()))
.app_data(web::Data::new(mysql_pool.clone()))
.service(web::scope("/api").configure(controller::api::config))
})
.bind(("0.0.0.0", 7777))?
.run()
.await?;
redist_stream_consumer.await.unwrap();
Ok(())
}
fn redis_stream_group() {
let client = redis::Client::open("redis://127.0.0.1/").expect("client");
let mut con = client.get_connection().expect("con");
let key = "s.order";
let group_name = "g1";
let consumer_name = "c1";
let _: Result<(), _> = con.xgroup_create_mkstream(key, group_name, "$");
let opts = StreamReadOptions::default()
.group(group_name, consumer_name)
.count(1)
.block(0);
loop {
let read_reply: StreamReadReply =
con.xread_options(&[key], &[">"], &opts).expect("read err");
for StreamKey { key, ids } in read_reply.keys {
for StreamId { id, map } in &ids {
log::info!("id:{} | key:{} | data:{:?}", id, key, map);
}
let id_strs: Vec<&String> = ids.iter().map(|StreamId { id, map: _ }| id).collect();
let _: usize = con.xack(key, group_name, &id_strs).expect("ack err");
}
}
}
When I use cargo r, I can run the program normally and get the sent messages, but when I execute ctrl+c, I can't exit the program.
Also I'm not sure if using web::block in the main function is correct and if there is a better way to run child threads
UPDATE: Tried using tokio::spawn, seems to work
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let redis_pool = utils::init::init_redis_pool();
let mysql_pool = utils::init::init_mysql_pool();
for consumer_index in 1..=2 {
let c_redis_pool = redis_pool.clone();
tokio::spawn(async move {
let mut con = c_redis_pool.get().await.unwrap();
let key = "s.order";
let group_name = "g1";
let consumer_name = &format!("c{consumer_index}");
let _: Result<(), _> = con.xgroup_create_mkstream(key, group_name, "$").await;
let opts = StreamReadOptions::default()
.group(group_name, consumer_name)
.count(1)
.block(5000);
loop {
let read_reply: StreamReadReply = con
.xread_options(&[key], &[">"], &opts)
.await
.expect("err");
for StreamKey { key, ids } in read_reply.keys {
for StreamId { id, map } in &ids {
log::info!(
"consumer: {} | id:{} | key:{} | data:{:?}",
consumer_name,
id,
key,
map
);
}
let id_strs: Vec<&String> =
ids.iter().map(|StreamId { id, map: _ }| id).collect();
let _: usize = con
.xack(key, group_name, &id_strs)
.await
.expect("ack err");
}
}
});
}
let serve = HttpServer::new(move || {
...
}
This can be done with the standard library by useing std::thread and then creating the thread and whatever you want the other thread to do in a closure
fn main() {
thread::spawn(|| {
println!("doing things in the thread!");
});
println!("doing things outside the thread.... how boring");
}
if you want to pass data between them, you can use std::sync::mpsc to transfer data between the threads safely and quickly, using let (item_one,item_two) = mpsc::channel();, like so
fn main() {
let (sender,receiver) = mpsc::channel();
thread::spawn(move || {
let message = String::from("This message is from the thread");
sender.send(message).unwrap();
});
let letter = receiver.recv().unwrap();
note that the main thread proceeds as normal until it comes to the .recv(), at which it either receives the data from the thread, or waits until the other thread is done.
in your example you could do something like
use std::sync::mpsc;
use std::thread;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
utils::init::init_envfile();
env_logger::init_from_env(env_logger::Env::new());
let port = get_env!("ACTIX_PORT", u16);
log::info!(
"starting HTTP server at http://{}:{}",
local_ipaddress::get().unwrap_or("localhost".to_string()),
port
);
let redis_pool = utils::init::init_redis_pool();
let mysql_pool = utils::init::init_mysql_pool();
let (consumer_sender,consumer_listener) = mpsc::channel();
thread::spawn(move || {
consumer_sender.send(redis_stream_group()).expect("You probably want to handle this case, but I'm too lazy");
});
let serve = HttpServer::new(move || {
let app_state = utils::init::AppState {
app_name: get_env!("APP_NAME", String),
pwd_secret: get_env!("PWD_SECRET", String),
jwt_secret: get_env!("JWT_SECRET", String),
jwt_exp: get_env!("JWT_EXP", i64),
};
App::new()
.app_data(web::Data::new(awc::Client::default()))
.app_data(web::Data::new(app_state))
.app_data(web::Data::new(redis_pool.clone()))
.app_data(web::Data::new(mysql_pool.clone()))
.wrap(actix_cors::Cors::default().allowed_origin_fn(|_, _| true))
.service(web::scope("/chat").configure(controller::chat::config))
.service(web::scope("/ws").configure(controller::ws::config))
.service(web::scope("/api").configure(controller::api::config))
});
if cfg!(debug_assertions) {
serve.bind(("0.0.0.0", port))?
} else {
let p = format!("/tmp/{}.socket", get_env!("APP_NAME", String));
let r = serve.bind_uds(&p)?;
let mut perms = std::fs::metadata(&p)?.permissions();
perms.set_readonly(false);
std::fs::set_permissions(&p, perms)?;
r
}
.run()
.await?;
let consumer = consumer_listener.recv().unwrap();
//then put things to do with the consumer here, or not idc
Ok(())
}
fn redis_stream_group() {
let client = redis::Client::open("redis://127.0.0.1/").expect("client");
let mut con = client.get_connection().expect("con");
let key = "s.order";
let group_name = "g1";
let consumer_name = "c1";
let _: Result<(), _> = con.xgroup_create_mkstream(key, group_name, "$");
let opts = StreamReadOptions::default()
.group(group_name, consumer_name)
.count(1)
.block(0);
loop {
let read_reply: StreamReadReply =
con.xread_options(&[key], &[">"], &opts).expect("read err");
for StreamKey { key, ids } in read_reply.keys {
for StreamId { id, map } in &ids {
log::info!("id:{} | key:{} | data:{:?}", id, key, map);
}
let id_strs: Vec<&String> = ids.iter().map(|StreamId { id, map: _ }| id).collect();
let _: usize = con.xack(key, group_name, &id_strs).expect("ack err");
}
}
}

How can I use threads to run this code simultaneously in rust?

I have a rust program that creates temporary email addresses using the mail.tm API, and I want to use threads to create emails simultaneously, to increase the speed. However, what I have tried, only results in printing "Getting email.." x amount of times, and exiting. I am unsure what to do about this. Any help or suggestions are appreciated.
use json;
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use reqwest;
use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE};
use std::{collections::HashMap, io, iter, vec::Vec};
use std::thread;
fn gen_address() -> Vec<String> {
let mut rng = thread_rng();
let address: String = iter::repeat(())
.map(|()| rng.sample(Alphanumeric))
.map(char::from)
.take(10)
.collect();
let password: String = iter::repeat(())
.map(|()| rng.sample(Alphanumeric))
.map(char::from)
.take(5)
.collect();
let body = reqwest::blocking::get("https://api.mail.tm/domains")
.unwrap()
.text()
.unwrap();
let domains = json::parse(&body).expect("Failed to parse domain json.");
let domain = domains["hydra:member"][0]["domain"].to_string();
let email = format!("{}#{}", &address, &domain);
vec![email, password]
}
fn gen_email() -> Vec<String> {
let client = reqwest::blocking::Client::new();
let address_info = gen_address();
let address = &address_info[0];
let password = &address_info[1];
let mut data = HashMap::new();
data.insert("address", &address);
data.insert("password", &password);
let mut headers = HeaderMap::new();
headers.insert(ACCEPT, HeaderValue::from_static("application/ld+json"));
headers.insert(
CONTENT_TYPE,
HeaderValue::from_static("application/ld+json"),
);
let res = client
.post("https://api.mail.tm/accounts")
.headers(headers)
.json(&data)
.send()
.unwrap();
vec![
res.status().to_string(),
address.to_string(),
password.to_string(),
]
}
fn main() {
fn get_amount() -> i32 {
let mut amount = String::new();
loop {
println!("How many emails do you want?");
io::stdin()
.read_line(&mut amount)
.expect("Failed to read line.");
let _amount: i32 = match amount.trim().parse() {
Ok(num) => return num,
Err(_) => {
println!("Please enter a number.");
continue;
}
};
}
}
let amount = get_amount();
let handle = thread::spawn(move || {
for _gen in 0..amount {
let handle = thread::spawn(|| {
println!("Getting email...");
let maildata = gen_email();
println!(
"Status: {}, Address: {}, Password: {}",
maildata[0], maildata[1], maildata[2]);
});
}
});
handle.join().unwrap();
}
Rust Playground example
I see a number of sub-threads being spawned from an outer thread. I think you might want to keep those handles and join them. Unless you join those sub threads the outer thread will exit early. I set up a Rust Playground to demonstrate ^^.
In the playground example, first run the code as-is and note the output of the code - the function it's running is not_joining_subthreads(). Note that it terminates rather abruptly. Then modify the code to call joining_subthreads(). You should then see the subthreads printing out their stdout messages.
let handle = thread::spawn(move || {
let mut handles = vec![];
for _gen in 0..amount {
let handle = thread::spawn(|| {
println!("Getting email...");
let maildata = gen_email();
println!(
"Status: {}, Address: {}, Password: {}",
maildata[0], maildata[1], maildata[2]);
});
handles.push(handle);
}
handles.into_iter().for_each(|h| h.join().unwrap());
});
handle.join().unwrap();

Gtk-rs application crashes at window.show_all() function

My Gtk-rs application crashes whenever I try calling the show_all() function.
In a simple application window, I have added a headerbar and a label. If I compile without adding the headerbar, the window works and shows the label as intended. However if I add the headerbar the window crashes.
use gio::prelude::*;
use gtk::{
prelude::*,
HeaderBarExt,
GtkWindowExt
};
fn gen_header_bar(subtitle: Option<String>) -> gtk::HeaderBar {
let header_bar = gtk::HeaderBar::new();
header_bar.set_title(Some(crate::consts::APP_NAME));
header_bar.set_show_close_button(true);
match subtitle {
Some(subtitle) => {
header_bar.set_subtitle(Some(&subtitle));
},
_ => {
}
}
header_bar
}
pub fn build_application_window() -> Result<(), Box<dyn std::error::Error>> {
let application = gtk::Application::new(
Some(crate::consts::APP_ID),
gio::ApplicationFlags::FLAGS_NONE,
)?;
application.connect_activate(move |app| {
let window = gtk::ApplicationWindow::new(app);
window.set_title(crate::consts::APP_NAME);
window.set_default_size(32 * 10, 200); // golden ratio
window.set_position(gtk::WindowPosition::Center);
let header_bar = gen_header_bar(None);
window.set_titlebar(Some(&header_bar));
window.add(&{
let label = gtk::Label::new(Some("Welcome!"));
label
});
window.show_all(); // crashes here
});
application.run(&std::env::args().collect::<Vec<_>>());
Ok(())
}
What is causing this?

Resources