Rust with Dart(Flutter): The efficient way to pass around big objects while following Rust's memory management and Dart's GC?


Dart is the language used by Flutter. It has GC, and is AOT compiled in the production environment. We can use Dart FFI to let Dart communicate with Rust via "extern C"-like things.

We can also use Dart_PostCObject from Rust to give an array of bytes from Rust to Dart. If we want, we do not need to copy the whole array, but can use the type called "ExternalTypedData". More details can be found in the example: provide helpers (or samples) for zero-overhead non-leaking bytes transfer between isolates in ffi · Issue #47270 · dart-lang/sdk · GitHub

Also, some information can be found (partially) at How to pass huge objects across isolates and/or ffi, without huge memory and cpu footprint? · Issue #1862 · dart-lang/language · GitHub if you are interested.


The problem setup: I have a Flutter app. I have some Rust code computing big images and talk to Flutter/Dart via FFI. For example, a typical call is:

  • Flutter calls Rust's ffi_create_an_image()
  • Rust's ffi_create_an_image spawns (for simplicity) a new thread, and let that thread to do heavy work of generating a big image (say, rust_worker_thread_create_an_image). But the ffi_create_an_image function returns immediately, thus Flutter will not be blocked.
  • After some time, rust_worker_thread_create_an_image() is finished. It uses Dart_PostCObject to post the image as an externalized typed data to Flutter, and Flutter can display it by receiving it in ReceivePort and use it to render the UI.

Now comes the problem: I want that big image to be manipulated later on again. Let's say, make it rotated by the angle given by the user via a text field in the UI (for simplicity).

Naive method is to copy the Uint8List to a region of memory allocated in Rust (suggested in dart-lang/language#1862), and pass it to Rust. But you see, we introduce one extra memory copy here.

Is it suggested (How to pass huge objects across isolates and/or ffi, without huge memory and cpu footprint? · Issue #1862 · dart-lang/language · GitHub) that I can use finalizers. But how should I do that with Rust's memory management system?

And moreover, how to deal with the mutability system? You know, after putting into an externalized typed data, the bytes corresponding to the array in memory can be changed freely without any constraints in Dart. But with Rust's mutability system, we know we should only have at most one guy who holds a mutable reference. Indeed, even if we do not think about the mutability system, but only consider common sense, it is still wrong: The memory can be modified by Dart/Flutter whenever we want; at the same time, it can also be modified (or at least read) by Rust code. This is race condition.

I come up with a naive way: if it is alive in externalized typed data (i.e. finalizer is not called yet), I should not use it in Rust. But this is silly, because Dart GC can happen very late after seconds, and before it is GCed, the finalizers will never be called, even if the pointer is already passed to Rust.

Thanks for any suggestions! I feel that the people in Rust forum are so expert and powerful.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.