-
Notifications
You must be signed in to change notification settings - Fork 473
Use unboxed closures throghout the code. #265
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
Conversation
The tricky bit in in `Timer`, which now works a bit differently. Specifically, we always stop the timer when the `Timer` object is dropped, because otherwise the closure might run past its lifetime 'a, rendering the code unsafe. I'm pretty sure the previous code is buggy.
Oh, I also added some tests for the Timer. |
This segfaults on my Mac w/ the following example: https://gist.github.com/drbawb/3488d741c436e6be9d39 My PR also fails this test, though for different reasons. (The closure doesn't live long enough; but that shouldn't be an issue as your boxed closure should live as long as I believe to get the semantics you desire you probably need to use an |
When using Taking the closure by value and doing the cast to a Though after further testing you are correct that it's possible for me to define a Timer which outlives the closure itself. -- For some reason your code with |
Yeah, that gist segfaults here too. Bizarrely, if you insert a Regarding Another thing: I'm using |
Oh, I know what the problem is -- I'm casting back a reference to |
Changing the |
So that it goes like this: &'a Box<FnMut() -> uint + 'a> ^ ^ 1 word 2 words (vtable and pointer) We can safely cast the reference to a void*, and at the same time we know that the reference is going to be valid as long as the closure is. Before, I had only the Box and took a reference to it when the time to transmute it into void* came. However, this was broken because the reference that we transmuted wasn't valid after Timer::start() finished. This is very ugly but I'm not sure how to do it differently.
I've been playing with this; I think is about as close as we can get to soundness. It passes both your test cases as well (after the mentioned improvements of course.) There's still one thing that worries me, per the SDL2 documentation: The timers are apparently run on a separate thread; I think we should be using closures which own their environment here ( |
Ah, you are right about moving the closure. That's nasty. Maybe the best option is to stay close to the SDL interface and let the user define his own |
This is what the interface would look like: bitonic@aa89bc0 . I think it's the way to go, instead of bending over backwards to try to mix C functions and rust closures. |
Looks good to me. -- By exposing callback and param I think it's even possible for people to implement a wrapper type which mimics the old behavior if they need it. |
I still feel free-standing callbacks are the way to go; but I've sort of figured something out in the course of playing with timer callbacks that capture-by-move. Even after implementing drbawb/rust-sdl2@7bb2e528 -- I was still getting errors under the following scenario:
(On the bright-side: my capture-by-move did make this a runtime error instead of a segfault. So at least I got memory safety back!) Hindsight being 20/20 the problem is a little clearer now: the Timer cannot move once What is happening is that the Timer (and thus the location of our Thus a truly safe API for this would simply need to require that once Food for thought in-case we want to iterate on a safe API for SDL timers. |
I'm pretty happy that people much clever than I am are sorting this out - are you happy for this to be merged? |
I didn't follow @drbawb last investigations, but I'm happy to merge as-is. After all, the timer functionality can easily and more idiomatically replicated with a Thread::spawn(move || {
loop {
...
sdl2::delay(...);
}
}); so as said previously I don't think this is an issue spending time on. |
Wonderful. In we go |
Use unboxed closures throghout the code.
The tricky bit in in
Timer
, which now works a bit differently.Specifically, we always stop the timer when the
Timer
object isdropped, because otherwise the closure might run past its lifetime 'a,
rendering the code unsafe. I'm pretty sure the previous code is buggy.
Note that this pull request is towards the same goal as #263, but does things differently. I don't use
TraitObject
s -- I don't think it's necessary -- and I already remove theremove_on_drop
option. I've been using rust for a week or so so review with care :).Edit: Following the discussion below, I've decided to drop support for Rust closures with SDL timers, and use
extern "C"
functions instead.