-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to pass channel into Closure? #1269
Comments
could it be because of:
? |
Sounds like @chpio has already solve this! With |
This comment was marked as abuse.
This comment was marked as abuse.
Yes. Or at least, I've used it a fair amount. |
This comment was marked as abuse.
This comment was marked as abuse.
@dakom It's just the natural consequence of Rust's memory management (i.e. no garbage collector). When the Rust compiler sees a closure, like this... move || {
do_something(foo);
} There are three possibilities: the closure can be The Rust compiler tries to make the closure as useful as possible. Since However, if the closure captures mutable references then that doesn't work. So then Rust tries to make the closure But if the closure captures a variable and then tries to move it, it cannot be Why can't the closure be let x = vec![];
let f = move || {
drop(x);
}; Here we are moving the variable The Rust compiler rightfully says that the type of f(); // Drops x
f(); // Drops x again! Oops, now we have a double-free, and therefore undefined behavior. Double dropping isn't the only bad thing that can happen, use-after-free could happen as well! This happens because How does the Rust compiler enforce that an pub fn map<U, F>(self, f: F) -> Option<U> where F: FnOnce(T) -> U {
match self {
Some(x) => Some(f(x)),
None => None,
}
} Notice that the type signature says that it accepts an What that means is that the What if you changed the The way to think of this is that there are two sides: the side which creates the closure, and the side which calls the closure. For creation, it is most useful to create For calling, it is most useful to accept Another way to think about it is that the caller decides what capabilities the closure has: if a caller (like On the other hand, if the caller accepts Right now wasm-bindgen only accepts Using let mut x = Some(vec![]);
let f = move || {
let y = x.take().unwrap();
drop(y);
}; Notice that the closure is no longer trying to move Since the closure doesn't move captured variables, it is no longer That means you will get a runtime panic if the closure is called multiple times, but that's better than undefined behavior! You can read more about the nitty gritty of Rust closures here: https://krishnasannasi.github.io/rust/syntactic/sugar/2019/01/17/Closures-Magic-Functions.html |
This comment was marked as abuse.
This comment was marked as abuse.
P.S. With the final example, you might be wondering why it no longer tries to move The reason is actually really simple: the Because On the other hand, the This is the sort of reasoning that the Rust compiler uses to determine whether a variable is moved or not. It's all based purely on the types. |
You're right, I've fixed it, thanks! |
This comment was marked as abuse.
This comment was marked as abuse.
yes, it's moved into the closure, but you call the closure (in case of a |
This comment was marked as abuse.
This comment was marked as abuse.
The Without the With the I think it's really important to understand how closures are implemented in Rust. Let's consider this example: let mut x: Option<Vec<u32>> = Some(vec![]);
let f = || {
let y = x.take().unwrap();
drop(y);
}; What does Rust compile the above code into? Well, it will be something like this: let mut x: Option<Vec<u32>> = Some(vec![]);
let f = {
struct Closure13925 {
x: &mut Option<Vec<u32>>,
}
impl FnMut<()> for Closure13925 {
type Output = ();
fn call_mut(&mut self, args: ()) -> () {
let y = self.x.take().unwrap();
drop(y);
}
}
Closure13925 {
x: &mut x,
}
}; Notice that the closure actually got converted into a struct (which has a unique name so it doesn't collide with other structs). The links I posted go into more detail about this. Each captured variable (in this case Another important thing is that captured variables get replaced with struct lookups: as you can see it replaced Lastly, notice that the struct contains a mutable reference to What happens when you instead use the let mut x: Option<Vec<u32>> = Some(vec![]);
let f = {
struct Closure13925 {
x: Option<Vec<u32>>,
}
impl FnMut<()> for Closure13925 {
type Output = ();
fn call_mut(&mut self, args: ()) -> () {
let y = self.x.take().unwrap();
drop(y);
}
}
Closure13925 {
x,
}
}; In this case it's exactly the same, except rather than the struct containing a mutable reference to However, notice that the So there are really two different kinds of movement here:
|
Incidentally, your original closure... move || {
err_sender.send(JsValue::from_str("foo"));
task.notify();
} ...would get compiled into something like this: struct Closure13925 {
err_sender: Sender<JsValue>,
task: Task,
}
impl FnOnce<()> for Closure13925 {
type Output = ();
fn call_once(self, args: ()) -> () {
self.err_sender.send(JsValue::from_str("foo"));
self.task.notify();
}
}
Closure13925 {
err_sender,
task,
} Since you used the And since You'll note that the That means that when you call the closure it will consume the entire closure struct. That has two implications:
However, since the closure struct implements Once you understand that closures are really just structs in disguise, everything makes perfect sense. |
This comment was marked as abuse.
This comment was marked as abuse.
Yes, the value must always exist. Rust does not have undefined values (except for The reason That's very different from having no value, which would be undefined behavior.
Yes, but it's actually the other way around:
And when you call a closure like The It's good to understand them, but you can't use them in your actual code, instead you use the magical So it's all just syntax sugar for structs + traits. |
Hi I got runtime error. I followed the take and drop advice. pub fn start_rpc_client_thread(
url: String,
jsonreq: String,
result_in: ThreadOut<String>,
on_message_fn: OnMessageFn,
) {
console_log!("jsonreq: {:?}",jsonreq);
let ws = WebSocket::new(&url).unwrap();
let ws_c = ws.clone();
let mut result_in_o = Some(result_in);
let on_message = {
Closure::wrap(Box::new(move |evt: MessageEvent| {
let msgg = evt.data()
.as_string()
.expect("Can't convert received data to a string");
console_log!("message event, received data: {:?}", msgg);
let res_e = (on_message_fn)(&msgg);
match res_e {
ResultE::None=>{},
ResultE::Close=>{
ws_c.close_with_code(1000).unwrap();
},
ResultE::S(s)=>{
let y = result_in_o.take().unwrap();
y.send(s).unwrap();
drop(y);
},
ResultE::SClose(s)=>{
let y = result_in_o.take().unwrap();
y.send(s).unwrap();
drop(y);
ws_c.close_with_code(1000).unwrap();
}
}
}) as Box<dyn FnMut(MessageEvent)>)
};
ws.set_onmessage(Some(on_message.as_ref().unchecked_ref()));
on_message.forget();
} Uncaught (in promise) RuntimeError: unreachable ::_get_request::h54ce5d76aa34c112 (wasm-function[108]:177) wasm-0012db0a-218:124 Uncaught RuntimeError: unreachable |
Fixed replacing channel with futures signal |
By wrapping the sender in an `Option`, like they suggest here: rustwasm/wasm-bindgen#1269 (comment).
Picking up from #1126
Consider the following:
If I remove that line:
err_sender.send(JsValue::from_str("foo"));
then it compiles fine. But with that line, I can't seem to get around the trait constraints.Any tips?
(I've left out the receiver polling just to keep the example a bit more concise - but we can imagine that polling it could move the state to
CounterState::Error
)The text was updated successfully, but these errors were encountered: