The key point for this gist repo is to indicate:
Don't misuse the pointers of the instances allocated in functions stack since they will be gone after finishing the function calling.
The sample code here is to demonstrate the problem I have when I am developing a Rust audio library. To play sounds, it needs to register callbacks to the underlying OS through audio APIs and then the callbacks will be fired periodically. The callback usually will come with at least 2 parameters:
- a buffer that will be filled with audio data
- a pointer indicating what variable calls the callback.
To create Rust APIs that can play audio, I create a struct for audio stream named Stream
and store all the stream related settings like sampling rate or channels in that struct. There is a struct method named new
that will create a Stream
instance that will be returnd. Before the instance is returned, I use the pointer of the instance as the callback's target pointer so that the callback knows what Stream
it belongs. When the callback is fired and run, the target pointer is dereferenced to a Stream
instance and then use it to call some functions to get the audio data to fill the buffer come with it. The process here looks logically reasonable, but it doesn't work. When the program runs, it causes a segmentation fault.
The root cause of the segmentation fault is that the callback function tries to convert a invalid pointer to a Stream
instance. The target pointer comes with the callback is pointed to a memory chunk that is abandoned after the struct method new
(stream::new(...)
) is called. Suppose we have a code like: let s = Stream::new(...)
. What let s = Stream::new(...)
does is to copy the returned value from stream::new(...)
and then abandon the stack memory to call stream::new(...)
. That's how normal function works in stack(if we don't have any variables in heap). That is, the memory of the created Stream
instance that I use its address as the callback's target pointer is marked unused after executing stream::new(...)
. That's why the program fails.
This is not a Rust-specific problem. It's a common problem for all languages. But if you do the same things as what stream::new(...)
does in C++'s constructor, it works. Since the address of this
is same as the address of the variable that calls constructor. Notice that the stream::new(...)
is not the constructor. In stead, it works like a static class/struct function that creates a class/struct instance and returns it. The key point is that you need to make sure the pointer points to the valid address you want.
To simulate the callback from underlying OS API, I create a external library to register and fire callbacks.
- The external library that simulates the callback behavior:
- ext.h
- ext.c
- Rust examples:
- problem.rs: Demonstrate the problem mentioned above
- solution.rs: Solution for the problem
Here is a simpler version of this problem, without exteranl library. In that repo, it also provides the comparison with how it works in other languages. See more details there.