Dushistov / flapigen-rs

Tool for connecting programs or libraries written in Rust with other languages
BSD 3-Clause "New" or "Revised" License
776 stars 59 forks source link

Threading / Runtimes / Safety #277

Open arrtchiu opened 5 years ago

arrtchiu commented 5 years ago

This question is a bit more general than rust_swig, but it's slightly related and I imagine many more people will be dealing with similar things as this fantastic library gains popularity.

I'm writing an app for both iOS and Android, for which I'm building a shared core in Rust and using rust_swig to manage both C and Java bindings.

What's tricky for me, though, is about threading and async stuff like network calls. We obviously can't run network i/o on the main thread (difficult/impossible compatibility with the UI frameworks), so the runtime would have to spool up background thread(s) in order to do its i/o work.

Since the i/o and other work would be happening in a background thread, simply calling into a foreign_callback! (for example a future on a web request), would be happening in a background thread. This is where I get confused - If I make calls to Rust objects here, I think I'm losing the thread-safety guarantees provided by the Rust compiler (it's gone into C land, therefore unsafe). Is that accurate?

Couple of ways I can think of solving it:

  1. Find a way to have rust_swig or my foreign_callbacks always dispatched on the main queue (i.e. dispatch_async(dispatch_get_main_queue()... on iOS). Doesn't work if the foreign_callback is expected to have a side effect or return a value, though.
  2. Expose parts of the runtime to the native app, letting the app control threading and just tell the runtime to "run" on a background thread. Then do thread safety manually.
  3. Mutex all the things and make the Rust lib completely thread-safe. Maybe not possible or practical.

Quite sure I'm not the first person to ever attempt this - do you possibly know any project out there you could point me at that does this well?

Dushistov commented 5 years ago

I'm losing the thread-safety guarantees provided by the Rust compiler (it's gone into C land, therefore unsafe). Is that accurate?

You obliviously loose any Rust guarantee when you use Rust from other language, not only thread safety, but also memory safety and so on thing. Obviously Java/C++ compiler have no idea about "one mutable reference" invariant, and about Send / Sync traits and so on things.

rust_swig is just attempt to reduce surface of problem, because of it uses the same rules for all API generation, if you have bug, you can just fix one rule and not all code.

Quite sure I'm not the first person to ever attempt this - do you possibly know any project out there you could point me at that does this well?

Yes, I am personally use rust_swig for writting GUI, because of Rust GUI frameworks are not mature enough.

Find a way to have rust_swig or my foreign_callbacks always dispatched on the main queue

That almost what I did/do. For example you want inform GUI that your background job finished, this may be Rust future or CPU intensive task that you spawn in different thread, you call callback and jump to other language (C++/Java/XXX), in this language you just post message to main GUI loop. In Java/Android, Qt/C++ you can post message to main thread event loop form any other thread, and subscribes in main GUI event loop peek messages and handle them.

For example, for C++/Qt I use QFuture. I write one rule to semi-automaticaly convert impl Future to QFuture, so I can spawn async Rust task and handle it in completely Qt way, so I don't care in which thread and how tokio runtime works, I get my data in Qt GUI thread via standard Qt mechanism - QFuture. And because of all multi-thread job is done on Rust, Rust compiler can guarantee that all is almost fine, except one my rule, that I should carefully review.

Internally this rule just pass message from tokio background thread to main one.

The similar thing is for Java/Android. I use LiveData. Because of my Java/Android doesn't often use Rust async future, I just use foreign_callback to invoke MutableLiveData.postValue and that's all (I don't write any rules for this). Android/SDK care about multithreading and invoke observers in the main thread, not in the thread where I call MutableLiveData.postValue. Again the same scheme post event in background thread to main event loop, and after some time code in main thread handle data.