rust-portaudio
rust-portaudio copied to clipboard
Stream callback defined as 'static
I was wondering if there is an important reason for the callback to be defined as 'static at:
https://github.com/RustAudio/rust-portaudio/blob/master/src/stream.rs#L1294
This implies that I can not wrap the logic for audio handling in an struct and use self within the callback (even if Send is implemented for it). It is not possible neither to pass any kind of reference to an struct that doesn't have static lifetime, which forces to create the audio handling struct just near the callback definition inside the same function.
For example, the following code is wrong:
fn run<'a>(handler: &'a mut Handler) -> Result<(), portaudio::error::Error> {
// ...
let callback = move |portaudio::OutputStreamCallbackArgs { buffer, .. }| {
for sample in buffer.chunks_mut(CHANNELS as usize) {
let (s0, s1) = handler.process();
sample[0] = s0 as f32;
sample[1] = s1 as f32;
}
portaudio::Continue
};
let mut stream = try!(pa_ctx.open_non_blocking_stream(settings, callback));
try!(stream.start());
// ...
}
And fails with:
error[E0477]: the type `[closure@src/main.rs:46:20: 55:6 handler:&'a mut Handler]` does not fulfill the required lifetime
--> src/main.rs:58:34
|
58 | let mut stream = try!(pa_ctx.open_non_blocking_stream(settings, callback));
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: type must outlive the static lifetime
The previous code only compiles when the Handler is instantiated inside run.
I believe it is necessary for the compiler in order to allow the callback to be moved into the inner closure which gets run on a separate thread by portaudio. Currently, there's no other way for rustc to know that the contents of the callback live long enough, "long enough" being whatever the lifetime of the portaudio stream's audio thread may be.
We may be able to improve this by assuming that a Stream's audio thread never lives longer than the Stream itself and enforcing that any data or references contained within the callback must live at least as long as the Stream. This would require unsafe, but should be OK as long as we can provide a way of guaranteeing that the portaudio stream's audio thread never lives longer than a Stream, perhaps by adding a lifetime to Stream and bounding the callback by this lifetime.
I'm afraid I don't have the time to look into this further myself. In the meantime, you should be able to get around this by wrapping your Handler in a std::sync::Arc<> instead of trying to use it in the callback via &'a mut. You would likely need something similar to this whether or not the 'static bound exists, as it looks like your Handler is accessed via both the main thread and the portaudio Stream's callback thread?
Sorry I don't have time to dig further, I hope this helps at least!
@mitchmindtree thank you very much for your elaborated response, it has been very helpful.
It makes sense to me that the callback lifetime is limited by the lifetime of the Stream as far as the Drop implementation for it takes care of stopping and releasing the corresponding portaudio stream state. This would allow to implement the callback in an struct I guess. If I have a chance I will explore it.
You were right, and by using Arc<Mutex<Handler>> I was able to call the mutable process() method on the Handler, so at least I can put all the processing logic under an struct (even if the callback has been defined outside it).
@chris-zen:
Not sure if you're still having troubles since this issue was opened a while ago, but I recently got this working with Trait types; see this repo at cc5c7e11dd. Of course the example is kind of weird since I'm effectively creating silence by adding a sine with its negative, but you get the idea.
I'm also messing around with a producer/consumer model and passing a chan::Receiver into run(), and it seems like it might be working out decently.
There is a simple trick that will give an audio stream a non-static lifetime, which I got from @Timmmm.
You add a lifetime parameter to the struct that wraps the stream, and you enforce that lifetime (and placate the compiler) by adding a ph: PhantomData<&'a ()> field to to the struct. The rest is pretty much mechanical. I've done it for coreaudio-rs since it seemed to be a minimal amount of work and I'm submitting a PR.
Note that I have temporarily abandoned soundio-rs because I couldn't figure out all the borrowing and lifetime stuff. The PhantomData is useful but I didn't realise it doesn't solve the 'non-borrowing lifetime' problem as also suffered by the SDL guys. More info here and here.