How can I use Rust with wasm-bindgen to create a closure that creates another closure with state? - rust

I am trying to create a small web application that will allow the user to drag and drop files onto the window. The files will then be read and their contents printed along with their filenames to the console. In addition, the files will be added to a list.
The equivalent code in JS could look something like:
window.ondragenter = (e) => {
e.preventDefault();
}
window.ondragover = (e) => {
e.preventDefault();
}
const allFiles = [];
const dropCallback = (e) => {
e.preventDefault();
const files = e.dataTransfer.files;
console.log("Got", files.length, "files");
for (let i = 0; i < files.length; i++) {
const file = files.item(i);
const fileName = file.name;
const readCallback = (text) => {
console.log(fileName, text);
allFiles.push({fileName, text});
}
file.text().then(readCallback);
}
};
window.ondrop = dropCallback;
When trying to do this in Rust, I run in to the problem that the outer closure needs to implement FnOnce to move all_files out of its scope again, which breaks the expected signature for Closure::wrap. And Closure::once will not do the trick, since I need to be able to drop multiple files onto the window.
Here is the code that I have tried without luck:
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;
macro_rules! console_log {
($($t:tt)*) => (web_sys::console::log_1(&JsValue::from(format_args!($($t)*).to_string())))
}
struct File {
name: String,
contents: String,
}
#[wasm_bindgen]
pub fn main() {
let mut all_files = Vec::new();
let drop_callback = Closure::wrap(Box::new(move |event: &web_sys::Event| {
event.prevent_default();
let drag_event_ref: &web_sys::DragEvent = JsCast::unchecked_from_js_ref(event);
let drag_event = drag_event_ref.clone();
match drag_event.data_transfer() {
None => {}
Some(data_transfer) => match data_transfer.files() {
None => {}
Some(files) => {
console_log!("Got {:?} files", files.length());
for i in 0..files.length() {
if let Some(file) = files.item(i) {
let name = file.name();
let read_callback = Closure::wrap(Box::new(move |text: JsValue| {
let contents = text.as_string().unwrap();
console_log!("Contents of {:?} are {:?}", name, contents);
all_files.push(File {
name,
contents
});
}) as Box<dyn FnMut(JsValue)>);
file.text().then(&read_callback);
read_callback.forget();
}
}
}
},
}
}) as Box<dyn FnMut(&web_sys::Event)>);
// These are just necessary to make sure the drop event is sent
let drag_enter = Closure::wrap(Box::new(|event: &web_sys::Event| {
event.prevent_default();
console_log!("Drag enter!");
}) as Box<dyn FnMut(&web_sys::Event)>);
let drag_over = Closure::wrap(Box::new(|event: &web_sys::Event| {
event.prevent_default();
console_log!("Drag over!");
}) as Box<dyn FnMut(&web_sys::Event)>);
// Register all the events on the window
web_sys::window()
.and_then(|win| {
win.set_ondragenter(Some(JsCast::unchecked_from_js_ref(drag_enter.as_ref())));
win.set_ondragover(Some(JsCast::unchecked_from_js_ref(drag_over.as_ref())));
win.set_ondrop(Some(JsCast::unchecked_from_js_ref(drop_callback.as_ref())));
win.document()
})
.expect("Could not find window");
// Make sure our closures outlive this function
drag_enter.forget();
drag_over.forget();
drop_callback.forget();
}
The error I get is
error[E0525]: expected a closure that implements the `FnMut` trait, but this closure only implements `FnOnce`
--> src/lib.rs:33:72
|
33 | ... let read_callback = Closure::wrap(Box::new(move |text: JsValue| {
| - ^^^^^^^^^^^^^^^^^^^^ this closure implements `FnOnce`, not `FnMut`
| _________________________________________________________|
| |
34 | | ... let contents = text.as_string().unwrap();
35 | | ... console_log!("Contents of {:?} are {:?}", name, contents);
36 | | ...
37 | | ... all_files.push(File {
38 | | ... name,
| | ---- closure is `FnOnce` because it moves the variable `name` out of its environment
39 | | ... contents
40 | | ... });
41 | | ... }) as Box<dyn FnMut(JsValue)>);
| |________________________- the requirement to implement `FnMut` derives from here
error[E0525]: expected a closure that implements the `FnMut` trait, but this closure only implements `FnOnce`
--> src/lib.rs:20:48
|
20 | let drop_callback = Closure::wrap(Box::new(move |event: &web_sys::Event| {
| - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this closure implements `FnOnce`, not `FnMut`
| _______________________________________|
| |
21 | | event.prevent_default();
22 | | let drag_event_ref: &web_sys::DragEvent = JsCast::unchecked_from_js_ref(event);
23 | | let drag_event = drag_event_ref.clone();
... |
33 | | let read_callback = Closure::wrap(Box::new(move |text: JsValue| {
| | -------------------- closure is `FnOnce` because it moves the variable `all_files` out of its environment
... |
50 | | }
51 | | }) as Box<dyn FnMut(&web_sys::Event)>);
| |______- the requirement to implement `FnMut` derives from here
error: aborting due to 2 previous errors; 1 warning emitted
For more information about this error, try `rustc --explain E0525`.
error: could not compile `hello_world`.
To learn more, run the command again with --verbose.
In a more complex example that I have not been able to reproduce in a simpler form, I get a more cryptic error, but I expect it to be related to the above:
error[E0277]: expected a `std::ops::FnMut<(&web_sys::Event,)>` closure, found `[closure#src/main.rs:621:52: 649:10 contents:std::option::Option<std::string::String>, drop_proxy:winit::event_loop::EventLoopProxy<CustomEvent>]`
--> src/main.rs:621:43
|
621 | let drop_callback = Closure::wrap(Box::new(move |event: &web_sys::Event| {
| ___________________________________________^
622 | | event.prevent_default();
623 | | let drag_event_ref: &web_sys::DragEvent = JsCast::unchecked_from_js_ref(event);
624 | | let drag_event = drag_event_ref.clone();
... |
648 | | }
649 | | }) as Box<dyn FnMut(&web_sys::Event)>);
| |__________^ expected an `FnMut<(&web_sys::Event,)>` closure, found `[closure#src/main.rs:621:52: 649:10 contents:std::option::Option<std::string::String>, drop_proxy:winit::event_loop::EventLoopProxy<CustomEvent>]`
I tried putting the all_files variable into a RefCell, but I still got a similar error. Are there any tricks or types that I can use to work around this in Rust and achieve what I want?

First, you are trying to copy name into a number of instances of File, but it must be cloned. Second, you need to properly ensure that all_files will be available whenever a closure wants to call it. One way to do so is by using a RefCell to enable multiple closures to write to it, and wrapping that in a Rc to ensure that it stays alive as long as any of the closures are alive.
Try this:
use std::{cell::RefCell, rc::Rc};
use wasm_bindgen::{prelude::*, JsCast, JsValue};
macro_rules! console_log {
($($t:tt)*) => (web_sys::console::log_1(&JsValue::from(format_args!($($t)*).to_string())))
}
struct File {
name: String,
contents: String,
}
#[wasm_bindgen]
pub fn main() {
let all_files = Rc::new(RefCell::new(Vec::new()));
let drop_callback = Closure::wrap(Box::new(move |event: &web_sys::Event| {
event.prevent_default();
let drag_event_ref: &web_sys::DragEvent = event.unchecked_ref();
let drag_event = drag_event_ref.clone();
match drag_event.data_transfer() {
None => {}
Some(data_transfer) => match data_transfer.files() {
None => {}
Some(files) => {
console_log!("Got {:?} files", files.length());
for i in 0..files.length() {
if let Some(file) = files.item(i) {
let name = file.name();
let all_files_ref = Rc::clone(&all_files);
let read_callback = Closure::wrap(Box::new(move |text: JsValue| {
let contents = text.as_string().unwrap();
console_log!("Contents of {:?} are {:?}", &name, contents);
(*all_files_ref).borrow_mut().push(File {
name: name.clone(),
contents,
});
})
as Box<dyn FnMut(JsValue)>);
file.text().then(&read_callback);
read_callback.forget();
}
}
}
},
}
}) as Box<dyn FnMut(&web_sys::Event)>);
// These are just necessary to make sure the drop event is sent
let drag_enter = Closure::wrap(Box::new(|event: &web_sys::Event| {
event.prevent_default();
console_log!("Drag enter!");
}) as Box<dyn FnMut(&web_sys::Event)>);
let drag_over = Closure::wrap(Box::new(|event: &web_sys::Event| {
event.prevent_default();
console_log!("Drag over!");
}) as Box<dyn FnMut(&web_sys::Event)>);
// Register all the events on the window
web_sys::window()
.and_then(|win| {
win.set_ondragenter(Some(drag_enter.as_ref().unchecked_ref()));
win.set_ondragover(Some(drag_over.as_ref().unchecked_ref()));
win.set_ondrop(Some(drop_callback.as_ref().unchecked_ref()));
win.document()
})
.expect("Could not find window");
// Make sure our closures outlive this function
drag_enter.forget();
drag_over.forget();
drop_callback.forget();
}
Note that if you are using multiple threads, you may want something other than RefCell (maybe Mutex instead). Also, I also changed uses of JsCast::unchecked_from_js_ref(x) to the more canonical x.as_ref().unchecked_ref().

Related

Is it possible to update a struct from a thread?

In Rust is it possible to update a struct from a thread started in one of the structs member functions?
I have an example below and the error I am getting is that you can't use self as a variable name.
use std::time::Duration;
use glib::{clone, Continue, MainContext, PRIORITY_DEFAULT};
use adw::{Application, ApplicationWindow};
use adw::prelude::*;
use std::thread;
const APP_ID: &str = "org.struct_threads";
fn main() {
let app = Application::builder().application_id(APP_ID).build();
app.connect_activate(build_ui);
app.run();
}
pub fn build_ui(app: &Application) {
let window = ApplicationWindow::builder()
.application(app)
.build();
let window_clone = window.clone();
let astruct = aStruct { aString : String::new(), aBool : false };
astruct.update_string();
while true {
println!("aString = {}", astruct.aString);
};
}
struct aStruct {
aString : String,
aBool : bool
}
impl aStruct {
pub fn update_string(&mut self) {
let (sender, receiver) = MainContext::channel(PRIORITY_DEFAULT);
thread::spawn(move || {
loop {
//let thisString = "";
if self.aString == "Value two" {
sender.send("Value one").expect("Could not send through channel");
//thisString = "Value two";
}
else {
sender.send("Value two").expect("Could not send through channel");
//thisString = "Value one";
}
//self.aStinrg = thisString.to_string();
thread::sleep(Duration::from_secs(10));
};
});
receiver.attach(
None,
clone!(#weak self => #default-return Continue(false),
move |reciever_string| {
self.aString = reciever_string;
Continue(true)
}
),
);
}
}
Error:
error: proc macro panicked
--> src/main.rs:99:13
|
99 | / clone!(#weak self => #default-return Continue(false),
100 | | move |reciever_string| {
101 | | self.aString = reciever_string;
102 | | Continue(true)
103 | | }
104 | | ),
| |____________^
|
= help: message: Can't use `self` as variable name. Try storing it in a temporary variable or rename it using `as`.
If I clone self and pass a normal variable name into the receiver I get an error stating that the struct does not implement Downgrade which doesn't seem to be implementable for booleans.
I get the same Downgrade error if I try and move this block into a non member function of the struct and call it separately.
Downgrade error:
error[E0277]: the trait bound `aStruct: Downgrade` is not satisfied
--> src/main.rs:99:13
|
99 | / clone!(#weak self_clone => #default-return Continue(false),
100 | | move |reciever_string| {
101 | | self.aString = reciever_string.to_string();
102 | | Continue(true)
103 | | }
104 | | ),
| |____________^ the trait `Downgrade` is not implemented for `aStruct`
|
= help: the following other types implement trait `Downgrade`:
&T
ATContext
AboutDialog
AboutWindow
Accessible
Action
ActionBar
ActionGroup
and 493 others
= note: required for `&aStruct` to implement `Downgrade`
= note: this error originates in the macro `clone` (in Nightly builds, run with -Z macro-backtrace for more info)
Finally if I just try and update the struct from within the thread using either self or a copy I get an error stating that the value does not live long enough. Is there way to update a struct from a thread?

How to return string value of state in hook?

Returning string state in use_effect_with_deps gives error.
use std::ops::Deref;
use yew::prelude::*;
#[hook]
pub fn use_hook_test() -> String
{
let first_load = use_state(|| true);
let hash_state = use_state(|| "".to_owned());
let hash_state_clone = hash_state.clone();
use_effect_with_deps(move |_| {
if *first_load {
wasm_bindgen_futures::spawn_local(async move {
hash_state_clone.set(format!("{:?}", "Hello"));
});
first_load.set(false);
}
|| {};
}, ());
hash_state_clone.deref().clone()
}
Error:
let hash_state_clone = hash_state.clone();
| ---------------- move occurs because `hash_state_clone` has type `yew::UseStateHandle<std::string::String>`, which does not implement the `Copy` trait
14 | use_effect_with_deps(move |_| {
| -------- value moved into closure here
...
18 | hash_state_clone.set(format!("{:?}", "Hello"));
| ---------------- variable moved due to use in closure
...
27 | hash_state_clone.deref().clone()
| ^^^^^^^^^^^^^^^^^^^^^^^^ value borrowed here after move
Here is a Yew Playground based on your example with some minor changes:
added an explicit scope to isolate the use_effect_with_deps
added a second hash_state.clone() after that scope
The result is somewhat nonsensical but compiles ok.
#[hook]
pub fn use_hook_test() -> String
{
let first_load = use_state(|| true);
let hash_state = use_state(|| "".to_owned());
{
let hash_state_clone = hash_state.clone();
use_effect_with_deps(move |_| {
if *first_load {
wasm_bindgen_futures::spawn_local(async move {
hash_state_clone.set(format!("{:?}", "Hello"));
});
first_load.set(false);
}
|| {};
}, ());
}
let hash_state_clone = hash_state.clone();
hash_state_clone.deref().clone()
}

rust error: captured variable cannot escape `FnMut` closure body

The following code tries to asynchronously update a master dataframe df (from polars package) after getting a msg by concatenating it.
I have seen the "duplicate" posts on stack overflow but still don't understand what I am doing wrong. I just want to mutably borrow the dataframe and update it, that's all! I tried it with a string, and it worked fine...
pub async fn new_handler(endpoint: &str) -> tokio::task::JoinHandle<()> {
// Make master df for this handler
let mut df = DataFrame::empty().lazy();
// Make a stream for this handler
let stream = new_stream(endpoint).await;
let handle = tokio::spawn(async move {
// let handle = tokio::spawn(async {
stream
.for_each(|msg| async move {
match msg {
Ok(msg) => {
// Parse the json message into a struct
let jsonmsg: AggTrade =
serde_json::from_str(&msg.to_string()).expect("Failed to parse json");
let s0 = Series::new(
"price",
vec![jsonmsg.price.parse::<f32>().expect("Failed to parse price")],
);
let s1 = Series::new(
"quantity",
vec![jsonmsg
.quantity
.parse::<f32>()
.expect("Failed to parse quantity")],
);
// Create new dataframe from the json data
let df2 = DataFrame::new(vec![s0.clone(), s1.clone()]).unwrap().lazy();
// append the new data from df2 to the master df
df = polars::prelude::concat([df, df2], false, true)
.expect("Failed to concat");
}
Err(e) => {
println!("Error: {}", e);
}
}
})
.await
});
handle
}
I get the following error:
error: captured variable cannot escape `FnMut` closure body
--> src/websockets.rs:33:29
|
27 | let mut df = DataFrame::empty().lazy();
| ------ variable defined here
...
33 | .for_each(|msg| async {
| ___________________________-_^
| | |
| | inferred to be a `FnMut` closure
34 | | match msg {
35 | | Ok(msg) => {
36 | | // Parse the json message into a struct
... |
58 | | df = polars::prelude::concat([df.clone(), df2.clone()], false, true)
| | -- variable captured here
... |
86 | | }
87 | | })
| |_____________^ returns an `async` block that contains a reference to a captured variable, which then escapes the closure body
|
= note: `FnMut` closures only have access to their captured variables while they are executing...
= note: ...therefore, they cannot allow references to captured variables to escape
The problem is that the closure passed to stream.for_each() can be called multiple times, but the df variable is moved into the closure when it's referenced by the df.clone() call.
Here's a self-contained minimal code example showing the same compilation error. If you uncomment the last lines in the function, it will fail to compile:
async fn fails_moved_into_closure_called_multiple_times() {
println!("fails_moved_into_closure_called_multiple_times():");
let mut df = vec![];
let closure = || async move {
let new_value = df.len();
println!("in the closure, pushing {}", new_value);
df.push(new_value);
};
let future = closure();
future.await;
let future2 = closure(); // FAIL
future2.await;
println!("final value: {:?}", df); // FAIL
}
In fact, Rust can't be sure that your for_each function doesn't call the closure multiple time concurrently in multiple threads. Here's a solution using Arc<Mutex<T>> that is thread-safe and fixes the ownership issues:
async fn fix_using_arc() {
println!("fix_using_arc():");
let df = Arc::new(Mutex::new(vec![]));
let closure = || async {
let my_df = Arc::clone(&df);
let mut shared = my_df.lock().unwrap();
let new_value = shared.len();
println!("in the closure, pushing {}", new_value);
shared.push(new_value);
};
let future = closure();
future.await;
let future2 = closure();
future2.await;
println!("final value: {:?}", df);
}

expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce` in actix web

I am using actix web and I am trying to return an async function in a closure but I am getting the following error:
error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce`
--> src/server.rs:134:33
|
133 | ... web::get().to(
| -- the requirement to implement `Fn` derives from here
134 | / ... move |router: web::Data<Arc<Router>>,
135 | | ... headers: web::Data<Arc<Headers>>,
136 | | ... stream: web::Payload,
137 | | ... req: HttpRequest| async {
| |_________________________________________________________-
138 | || ... start_web_socket(req, stream, params).await
139 | || ... },
| || ^
| ||___________________________|
| |____________________________this closure implements `FnOnce`, not `Fn`
| closure is `FnOnce` because it moves the variable `params` out of its environment
error: aborting due to previous error; 1 warning emitted
This is the snippet that is resulting in the error. I have tried to document the code as much as I could.
I have tried moving out the variables in and out of the move blocks and have tried placing them at various places but without success.
What should be done instead of this?
pub fn start(
&mut self,
py: Python,
url: String,
port: u16,
socket: &PyCell<SocketHeld>,
name: String,
workers: usize,
) -> PyResult<()> {
if STARTED
.compare_exchange(false, true, SeqCst, Relaxed)
.is_err()
{
println!("Already running...");
return Ok(());
}
println!("{}", name);
let borrow = socket.try_borrow_mut()?;
let held_socket: &SocketHeld = &*borrow;
let raw_socket = held_socket.get_socket();
println!("Got our socket {:?}", raw_socket);
let router = self.router.clone();
let headers = self.headers.clone();
let directories = self.directories.clone();
let workers = Arc::new(workers);
let asyncio = py.import("asyncio").unwrap();
let event_loop = asyncio.call_method0("new_event_loop").unwrap();
asyncio
.call_method1("set_event_loop", (event_loop,))
.unwrap();
let event_loop_hdl = PyObject::from(event_loop);
thread::spawn(move || {
//init_current_thread_once();
actix_web::rt::System::new().block_on(async move {
let addr = format!("{}:{}", url, port);
println!("The number of workers are {}", workers.clone());
HttpServer::new(move || {
let mut app = App::new();
let event_loop_hdl = event_loop_hdl.clone();
let directories = directories.read().unwrap();
let router_copy = router.clone();
// this loop matches three types of directory serving
// 1. Serves a build folder. e.g. the build folder generated from yarn build
// 2. Shows file listing
// 3. Just serves the file without any redirection to sub links
for directory in directories.iter() {
if let Some(index_file) = &directory.index_file {
app = app.service(
Files::new(&directory.route, &directory.directory_path)
.index_file(index_file)
.redirect_to_slash_directory(),
);
} else if directory.show_files_listing {
app = app.service(
Files::new(&directory.route, &directory.directory_path)
.redirect_to_slash_directory()
.show_files_listing(),
);
} else {
app = app
.service(Files::new(&directory.route, &directory.directory_path));
}
}
app = app
.app_data(web::Data::new(router.clone()))
.app_data(web::Data::new(headers.clone()));
let web_socket_map = router_copy.get_web_socket_map().unwrap();
for elem in (web_socket_map).iter() {
let route = elem.key().clone();
let params = elem.value().clone();
app = app.route(
&route,
web::get().to(
move |router: web::Data<Arc<Router>>,
headers: web::Data<Arc<Headers>>,
stream: web::Payload,
req: HttpRequest| async {
start_web_socket(req, stream, params).await
},
),
)
}
app.default_service(web::route().to(move |router, headers, payload, req| {
pyo3_asyncio::tokio::scope_local(event_loop_hdl.clone(), async move {
index(router, headers, payload, req).await
})
}))
})
.keep_alive(KeepAlive::Os)
.workers(*workers.clone())
.client_timeout(0)
.listen(raw_socket.try_into().unwrap())
.unwrap()
.run()
.await
.unwrap();
});
});
----UPDATE----
Thank you for the suggestions. On updating the params, I was able to get rid of the original error but I am getting a similar but new error:
error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce`
--> src/server.rs:134:33
|
133 | ... web::get().to(
| -- the requirement to implement `Fn` derives from here
134 | / ... |router: web::Data<Arc<Router>>,
135 | | ... headers: web::Data<Arc<Headers>>,
136 | | ... stream: web::Payload,
137 | | ... req: HttpRequest| async move {
| |_________________________________________________________-
138 | || ... start_web_socket(req, stream, params.clone()).await
139 | || ... },
| || ^
| ||___________________________|
| |____________________________this closure implements `FnOnce`, not `Fn`
| closure is `FnOnce` because it moves the variable `params` out of its environment
The new snippet looks like this:
let web_socket_map = router_copy.get_web_socket_map().unwrap();
for elem in (web_socket_map).iter() {
let route = elem.key().clone();
let params = elem.value().clone();
app = app.route(
&route,
web::get().to(
|router: web::Data<Arc<Router>>,
headers: web::Data<Arc<Headers>>,
stream: web::Payload,
req: HttpRequest| async move {
start_web_socket(req, stream, params.clone()).await
},
),
)
}
I have tried cloning params inside the async move but it still is giving the same error.
web::get() returns a Route, and web::get().to(...) is a Route method that expects a Handler. The Handler is expected to be an async function (async fn) - a function that returns a Future when called.
The problem is that in your code you are passing an async block, which IS a future.
An async block is a variant of a block expression which evaluates to a future.
So your code:
async move |...| { // Future
start_web_socket(req, stream, params.clone()).await
}
Could be converted to:
move |...| { // Handler Fn
async move { // Future
start_web_socket(req, stream, params.clone()).await
}
}
and that is equivalent to:
move |...| { // Handler Fn
start_web_socket(req, stream, params.clone()) // Future
}
because when you call an async fn start_web_socket without .await, you get a Future.
A tip for debugging such things is to assign things to intermediate variables, and checking the types that compiler deduces for them.

Rust declare first assign later pattern

I have a bi-directional grpc stream that acts as bridge to a kafka cluster. When the stream is first initialised, I was to create the kafka consumer and start using it.
To do so, I thought of initialising an empty consumer, waiting for the first input, then assigning a created consumer to an empty one. I tried to do so by following the pattern here.
https://doc.rust-lang.org/rust-by-example/variable_bindings/declare.html
Rust is throwing a possibly-unitialized variable error, is this because it is being initialised in an asynchronous stream?
use std::pin::Pin;
use futures::{Stream, StreamExt};
use kafka::consumer::{Consumer, FetchOffset, GroupOffsetStorage};
use tonic::transport::Server;
use tonic::{Request, Response, Status};
use bridge::kafka_stream_server::{KafkaStream, KafkaStreamServer};
use bridge::{KafkaResponse, PublishRequest};
pub mod bridge {
tonic::include_proto!("bridge"); // The string specified here must match the proto package name
}
#[derive(Default)]
pub struct KafkaStreamService {}
pub fn create_kafka_consumer(topic: String) -> Consumer {
Consumer::from_hosts(vec!["localhost:9092".to_owned()])
.with_topic(topic.to_owned())
.with_fallback_offset(FetchOffset::Latest)
.with_group("".to_owned())
.with_offset_storage(GroupOffsetStorage::Kafka)
.create()
.unwrap()
}
#[tonic::async_trait]
impl KafkaStream for KafkaStreamService {
type SubscribeStream =
Pin<Box<dyn Stream<Item = Result<KafkaResponse, Status>> + Send + Sync + 'static>>;
async fn subscribe(
&self,
request: Request<tonic::Streaming<PublishRequest>>,
) -> Result<Response<Self::SubscribeStream>, Status> {
println!("Initiated stream!");
let mut stream = request.into_inner();
let mut consumer_created_flag: bool = false;
let consumer: Consumer; //declared here
let output = async_stream::try_stream! {
while let Some(publication) = stream.next().await {
let message = publication?;
let topic = message.topic.clone();
if consumer_created_flag == false {
consumer = create_kafka_consumer(topic); //error occurs here
consumer_created_flag = true;
}
let reply = bridge::KafkaResponse {
content: format!("Hello {}!", "world"),
};
yield reply.clone();
}
};
Ok(Response::new(Box::pin(output) as Self::SubscribeStream))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "[::1]:50051".parse().unwrap();
println!("KafkaService listening on: {}", addr);
let svc = KafkaStreamServer::new(KafkaStreamService::default());
Server::builder().add_service(svc).serve(addr).await?;
Ok(())
}
EDIT: verbose error as requested:
error[E0381]: use of possibly-uninitialized variable: `consumer`
--> src/server.rs:42:22
|
42 | let output = async_stream::try_stream! {
| ______________________^
43 | | while let Some(publication) = stream.next().await {
44 | | let message = publication?;
45 | | let topic = message.topic.clone();
46 | | if consumer_created_flag == false {
47 | | consumer = create_kafka_consumer(topic);
| | -------- use occurs due to use in generator
... |
54 | | }
55 | | };
| |_________^ use of possibly-uninitialized `consumer`
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
The declare first pattern only works with basic control flow (if, match, {}, etc). It falls apart when referenced or moved into another object, like an async block or a closure:
fn main() {
let val: i32;
let func = move || {
val = 5;
};
}
error[E0594]: cannot assign to `val`, as it is not declared as mutable
--> src/main.rs:4:9
|
2 | let val: i32;
| --- help: consider changing this to be mutable: `mut val`
3 | let func = move || {
4 | val = 5;
| ^^^^^^^ cannot assign
error[E0381]: use of possibly-uninitialized variable: `val`
--> src/main.rs:3:16
|
3 | let func = move || {
| ^^^^^^^ use of possibly-uninitialized `val`
4 | val = 5;
| --- use occurs due to use in closure
A potential fix is to move its declaration into the try_stream! macro:
let output = async_stream::try_stream! {
let mut consumer_created_flag: bool = false;
let consumer: Consumer;
while let Some(publication) = stream.next().await {
let message = publication?;
let topic = message.topic.clone();
if consumer_created_flag == false {
consumer = create_kafka_consumer(topic);
consumer_created_flag = true;
}
let reply = KafkaResponse {
content: format!("Hello {}!", "world"),
};
yield reply.clone();
}
};
However, this causes a new error because you're potentially assigning to it twice (the compiler doesn't know that consumer_created_flag is guarding it):
error[E0384]: cannot assign twice to immutable variable `consumer`
--> src\lib.rs:1348:21
|
44 | let consumer: Consumer; //declared here
| -------- help: make this binding mutable: `mut consumer`
...
49 | consumer = create_kafka_consumer(topic); //error occurs here
| ^^^^^^^^ cannot assign twice to immutable variable
Fortunately a quick fix is to simply make consumer mutable. And then the only thing the compiler complains about is that it is unused, but I figure there's a reason you've put it there.

Resources