Skip to main content

Safety and CI

CI

We heavily use CI to ensure everything is going well, including but not limited to memory safety. Shortly speaking, we deploy not only Valgrind but also sanitizers (ASAN, MSAN, LSAN) in the CI, which are commonly used tools to check safety issues.

As for all things that CI checks, please refer to the CI configuration for full details, and here is a brief list:

  • Use Valgrind to check safety
  • Use Sanitizers (ASAN, MSAN, LSAN) to check safety
  • Testing (on Android, iOS, Windows, MacOS, Linux, Web)
  • Run performance benchmarks
  • Run flutter_rust_bridge_codegen
  • Linters and code formatters
  • Post-release tests (check the released binary)
  • Test steps mentioned in quickstart
  • ...

Usage

This library seems to be used by (I want to say "is used by" but I need to be humble ;) ) many people (as can be seen from pub.dev popularity, blogs on the Internet, etc). Therefore, bugs should be usually easily spotted and raised in the issue tracker. However, during the past two years, there have not been issues about weird memory issues, hitting undefined behavior, etc.

I also personally use flutter_rust_bridge heavily in my Flutter project, which is in production and it works quite well. If I observe any problems, I will surely fix it in this library, but again I have not seen any safety-related issues.

Safety of unsafe

It is inevitable to write unsafe code, as long as we want to use Rust with another language. The thing we can do and have done is, make the unsafe blocks carefully limited & encapsulated, make the code clear and well designed, use strong checkers in CI, etc. I am happy to see that, nobody reported bugs related to this!

The vast majority of the code are written in safe Rust and (safe) Dart. The unsafe code mainly happens when we need to leak a Rust Vec into a raw pointer, and later assemble it back, in order to pass it to the Dart side. This is widely used pattern - there are official Rust doc, and answers on StackOverflow about this also have high votes.

In terms of safety, there are two categories of codecs, and you can freely choose whichever you like. One category uses bare minimal unsafe code, and the other maximizes performance. Below, we only discuss the latter - since it is the harder case.

The unsafe logic is made as separated as possible with other safe logic, with as clear semantics as possible. For example, instead of combining all logic into a single bigger RustOpaque Dart class, I choose to extract a RustArc Dart class, which encapsulates the Rust Arc inside it. Then, high-level RustOpaque logic will be safe as long as it uses RustArc's public API, and at the same time, RustArc is easy to audit because it has a clear semantics.

As another example, without extra work, DartOpaque is !Send and !Sync, because the underlying objects really cannot be used in other threads. But we unsafe impl Send/Sync, because we forbid users from touching or dropping it in other threads. In V1, most logic were put inside the single DartOpaque class, mixing things with this unsafe part. In V2, this unsafe logic is extracted to GuardedBox, and the DartOpaque becomes pure safe code.

These can be further seen in details in the contributor guides.

In addition, different parts are isolated. For example, if your project does not use opaque types, then surely any code related to them will not be used at all.