-
Notifications
You must be signed in to change notification settings - Fork 176
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
[Question] Why does event listener need 'static lifetime #239
Comments
The The reason is because JavaScript can call the closure at any time (including asynchronously). Imagine that you have some stack variable which the closure is using: let mut x = "foobar";
foo.add_event_listener(|e: MouseClickEvent| {
x = "quxcorge";
}); There's two problems here:
In order to fix this, you can simply let mut x = "foobar";
foo.add_event_listener(move |e: MouseClickEvent| {
x = "quxcorge";
}); Since the closure now owns the variable However, the downside of the above approach is that you can only access the variable within a single closure. If you have some state that you want to share with many closures, then you must use let x = Rc::new(RefCell::new("foobar"));
{
let x = x.clone();
foo.add_event_listener(move |e: MouseClickEvent| {
let x = x.borrow_mut();
*x = "quxcorge";
});
}
{
let x = x.clone();
bar.add_event_listener(move |e: MouseClickEvent| {
let x = x.borrow_mut();
*x = "quxcorge";
});
} This works because As another alternative, you can use lazy_static! {
static ref X: Mutex<&'static str> = Mutex::new("foobar");
}
foo.add_event_listener(|e: MouseClickEvent| {
let x = X.lock().unwrap();
*x = "quxcorge";
});
bar.add_event_listener(|e: MouseClickEvent| {
let x = X.lock().unwrap();
*x = "quxcorge";
}); This still has a performance cost (from the Technically you could use a mutable In summary: If you have some state which only needs to be used by a single closure, just If you have some state which needs to be shared between multiple closures, then you will pay a performance cost. The performance cost is extremely small, so it's not worth worrying about. To put it into perspective, In addition, using In contrast, every object in JavaScript is heap-allocated, and the overhead is generally much worse than an integer check/set! |
Wow this write-up was excellent. Thanks for some of the rust background. The additional option of lazy_static is one I did not think of. I'm also not informed of the differences between These statements were pretty helpful for me:
Again, I did not know too much about how well RefCell is implemented. If it does a simple int set/clr then I already feel a lot better about using it. One reason I could see for preferring state stay on the stack is that you might get better performance accessing a reference on the stack than an object on heap. It may be trivial, and it may be a wasm browser implementation detail. Some additional thoughts: Thanks, and keep up the good work! |
I personally find it very enlightening to read the source code for the standard library. Everything's in Rust, so it's usually quite easy to read.
In the case of When you call When it creates the And that's pretty much it! It might seem like a lot of code, but because it's all inlined it will end up being extremely small and fast.
I'm not 100% sure, but I'm pretty sure there's zero difference between accessing a stack reference and a heap reference. They both end up going into the wasm linear memory (just in different spots).
It both is and it isn't. It's extremely annoying when you first find out about it, but after you get used to using For my own programs, I usually create a single struct State {
foo: Cell<bool>,
bar: RefCell<String>,
}
fn main() {
let state = Rc::new(State {
foo: Cell::new(false),
bar: RefCell::new("".to_owned()),
});
} I also created a simple foo.add_event_listener(clone!(state => move |e: MouseClickEvent| {
state.foo.set(true);
let bar = state.bar.borrow_mut();
// ...
})); This significantly improves the ergonomics of using Alternatively, rather than putting
It's a great idea (which I had thought about before), but unfortunately it doesn't work. Consider this simple example: let mut x = "foobar";
let listener = foo.add_event_listener(|e| {
x = "quxcorge";
});
std::mem::forget(listener); In this case we're using There's many ways to leak things in Rust (including The exact same problem happened with Also, even if we could tie the event listener token to the stack, it wouldn't be as useful as you might think. Consider this example: fn add_listener<'a>(foo: &'a Node) -> EventListener<'a> {
let mut x = "foobar";
let listener = foo.add_event_listener(|e| {
x = "quxcorge";
});
listener
} In this case we're returning the And if you create the listeners in the fn main() {
let foo = ...;
let mut x = "foobar";
let listener1 = foo.add_event_listener(|e| {
x = "quxcorge";
});
let listener2 = foo.add_event_listener(|e| {
x = "quxcorge";
});
} In this case the event listener tokens are tied to the stack, so as soon as the |
Wow I did not know this was a thing! This will be my new favorite link when browsing rust docs.
This makes a lot of sense. I assumed it might not matter due to implementation details like the linear memory. ... Holy cow you must have spent sometime putting that last explanation together. I really appreciate it. I think I can learn to live comfortably using the |
It even works for third-party crates like stdweb!
Sure, I'm glad I was able to help. |
I'm starting out with some of the examples and am exploring ways to manage mutable state. One restriction I have run into is the
'static
requirement for the event listener closure. This is from theIEventTarget
trait:In the examples, a
Rc<RefCell<_>>
is passed to the closure so we can safely mutate the state. It is a convenient tool for getting around the borrow checker. BothRc
andRefCell
add to runtime overhead which makes me a little uncomfortable, so I started looking for a different solution. The best one I have so far is using astatic mut
state and wrapping all usage inside anunsafe
block. This feels wrong to me.Why is the trait written this way, and what other options do I have for mutating my state in the event listener closure?
The text was updated successfully, but these errors were encountered: