Sharing variable in two closures - rust

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

Related

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

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

Getting Variable from other function

I don't know how to get (_s) variable from other function. Maybe there is a way to reformat this script, so it could work or any other way.
fn shapes(data: usize) {
let romb = "hello";
let s_array = [romb];
let mut items_array = -1;
print!("{}", s_array[data]);
if items_array == _s { //<-------this _s variable is not in this function, so it doesn't work
} else {
println!("error");
}
for x in &s_array {
items_array = items_array + 1;
}
print!("{}", items_array);
}
fn main() {
let mut _s = String::new();
let _b1 = std::io::stdin()
.read_line(&mut _s)
.expect("failed to read line");
let mut _s = _s.trim_end();
let _s_int = _s.parse::<usize>().unwrap();
shapes(_s_int);
}
You need to add another parameter to your shapes function to pass _s. So extend the prototype of your function like:
fn shapes(data: usize, _s: String) {
//Use _s here
}
to call it like:
shapes(_s_int, _s);

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 do I simultaneously read messages from multiple Tokio channels in a single task?

I'd like to both read and process messages from two channels and construct another message and send this message via another channel.
Messages from the two channels are received at different frequencies (as per sleep).
Example: "foo1" and "bar1" are received, so we process them and form "foo1bar1". "foo2" is received ("bar2" will be received in 2sec), so we will process it as "foo2bar1". "foo3" is received, so "foo3bar1" is constructed. When "bar2" is received, then we get "foo4bar2" and so on.
In the current implementation, since the two tasks don't communicate with one another, I cannot do the "fooNbarM" construction.
use std::time::Duration;
use tokio;
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
use tokio::time::sleep;
use futures::future::join_all;
async fn message_sender(msg: &'static str, foo_tx: UnboundedSender<Result<&str, Box<dyn std::error::Error + Send>>>) {
loop {
match foo_tx.send(Ok(msg)) {
Ok(()) => {
if msg == "foo" {
sleep(Duration::from_millis(1000)).await;
} else {
sleep(Duration::from_millis(3000)).await;
}
}
Err(_) => {
println!("failed to send foo");
break;
}
}
}
}
#[tokio::main]
async fn main() {
let result: Vec<&str> = vec![];
let (foo_tx, mut foo_rx): (
UnboundedSender<Result<&str, Box<dyn std::error::Error + Send>>>,
UnboundedReceiver<Result<&str, Box<dyn std::error::Error + Send>>>,
) = tokio::sync::mpsc::unbounded_channel();
let (bar_tx, mut bar_rx): (
UnboundedSender<Result<&str, Box<dyn std::error::Error + Send>>>,
UnboundedReceiver<Result<&str, Box<dyn std::error::Error + Send>>>,
) = tokio::sync::mpsc::unbounded_channel();
let foo_sender_handle = tokio::spawn(async move {
message_sender("foo", foo_tx).await;
});
let foo_handle = tokio::spawn(async move {
while let Some(v) = foo_rx.recv().await {
println!("{:?}", v);
}
});
let bar_sender_handle = tokio::spawn(async move {
message_sender("bar", bar_tx).await;
});
let bar_handle = tokio::spawn(async move {
while let Some(v) = bar_rx.recv().await {
println!("{:?}", v);
}
});
let handles = vec![foo_sender_handle, foo_handle, bar_sender_handle, bar_handle];
join_all(handles.into_iter()).await;
}
Cargo.toml
[package]
name = "play"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tokio = { version = "1.16.1", features = ["full"] }
futures = "0.3.21"
Use tokio::select to wait for either channel to become ready:
use futures::future; // 0.3.19
use std::time::Duration;
use tokio::{
sync::mpsc::{self, UnboundedSender},
time,
}; // 1.16.1
async fn message_sender(msg: &'static str, foo_tx: UnboundedSender<String>) {
for count in 0.. {
let message = format!("{msg}{count}");
foo_tx.send(message).unwrap();
if msg == "foo" {
time::sleep(Duration::from_millis(100)).await;
} else {
time::sleep(Duration::from_millis(300)).await;
}
}
}
#[tokio::main]
async fn main() {
let (foo_tx, mut foo_rx) = mpsc::unbounded_channel();
let (bar_tx, mut bar_rx) = mpsc::unbounded_channel();
let foo_sender_handle = tokio::spawn(message_sender("foo", foo_tx));
let bar_sender_handle = tokio::spawn(message_sender("bar", bar_tx));
let receive_handle = tokio::spawn(async move {
let mut foo = None;
let mut bar = None;
loop {
tokio::select! {
f = foo_rx.recv() => foo = f,
b = bar_rx.recv() => bar = b,
}
if let (Some(foo), Some(bar)) = (&foo, &bar) {
println!("{foo}{bar}");
}
}
});
future::join_all([foo_sender_handle, bar_sender_handle, receive_handle]).await;
}
You also have to handle the case where only one message has been received yet, so Option comes in useful.

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

Resources