I have the following code where I'm trying to express a relationship between 'dsp' and 'UI'. A specific FUI trait ('derived' from the UI one) keeps the &'a mut f32 type in a Vec. UI (and derived FUI) cannot live more that 'dsp' since FUI keeps references on part of 'dsp' state. This fails with lifetime issues I could not solve. Any idea ? Thanks.
I presume that's part of a more complex problem, since here you could copy the f32 instead of borrowing it.
So in general, trying to store &mut in a struct will lead to misery. Don't try to use borrows like you'd use pointers in C. Treat &mut as a strictly temporary, scope-limited permission to write to an object.
For passing things around:
Prefer owned/cloned/copied values when creating objects. Give an object instead of lending it.
If it has to be shared and writeable, use Rc<Mutex> or RefCell.
Yes, this is indeed part of a more complex problem: basically "sharing" a f32 memory zone between 'UI' (and by extension FUI and other UI..) and 'dsp'. UIs are going to write the f32 zone and 'dsp' is going to read the f32 memory zone it from another (real-time) thread.
We also need to avoid mutex like access since this is going to be used in the real-time audio context. In C++ we were simply writing the zone in 'UIs' and reading in 'dsp', and since f32 read/write is atomic on the machine were are using, it was Ok.
So I see that rewriting the same pattern in Rust is more challenging: so to summarize "sharing" a f32 memory zone between two (or even more writer threads...), and a single reader 'dsp' real-time thread. 'UIs' lifetime typical has to match 'dsp' lifetime.
Rust is trying to prevent you from having unsynchronized shared mutable data. The whole type system is built to prevent accidentally doing what you're trying to do, so you'll have to explain to Rust that you know what you're doing
RefCell still checks for race conditions, so it's not the right solution here.
In Rust there's Cell and UnsafeCell to "cheat" the type system to allow unrestricted read/write access.
Because hardware in practice (even x86) doesn't support it, so it wouldn't be all that useful. There are some atomic memory operations on x86, however they are for integers. C language does provide _Atomic double type, but actually doing operations like += 1 on x86 CPU involves a spinlock when multiple threads are trying to add a value.
You could convert the f32/64 to their raw bits (u32/64), and then use the integer atomics. The atomics would just be "raw storage" and you'd convert from/to the float as needed.
You have to be careful with this - compilers may do "funny" stuff when performing plain loads/stores. In C++, you'd probably want atomic loads/stores with memory_order_relaxed, rather than naked load/store. Similarly in Rust.
Yes, you have to explicitly implement Sync, as a statement to the compiler that what you're doing is thread-safe. And if you have a data race in it, LLVM's optimizer will then feel free to break your code, as that is Undefined Behaviour.