rust_in_flutter 1.2.5 rust_in_flutter: ^1.2.5 copied to clipboard
Easily integrate Rust to make your Flutter app blazingly fast!
π Rust-In-Flutter #
Easily integrate Rust to make your Flutter app blazingly fast!
Designed with ease of use, future scalability, and exceptional performance in mind, this lightweight framework handles all the complicated aspects behind the scenes. Simply add this package to your Flutter project and you're ready to write Rust!
Benefits #
- Rust integration with the ability to use an arbitrary number of library crates
- Being able to use an existing Rust crate as it is
- No messing with sensitive build files such as CMake, Gradle, Podfile, etc
- No complicated code generation during development
- Defining unlimited RESTful API endpoints without much effort
- Async interaction with easy request from Dart and response from Rust
- Streaming from Rust to Dart
- Restarting Rust logic on Dart's hot restart
- Minimal overhead
- No memory copy when sending native data
Platform Support #
All the challenging build settings are automatically handled by this package. Note that the files in your Flutter project are not affected.
- β Linux: Tested and supported
- β Android: Tested and supported
- β Windows: Tested and supported
- β macOS: Tested and supported
- β iOS: Tested and supported
- βΈοΈ Web: Not now but considered
If you have any suggestions or want to report a bug, please leave it as an issue or a pull request. We will try to respond as quickly as possible.
Why Use Rust? #
While Dart is an amazing object-oriented modern language, its performance sometimes does not meet the requirements because it's non-native garbage-collected language. That's where Rust comes into play. Rust's performance is known to be roughly about 2~40 times faster than Dart, not to mention the ability to utilize multiple threads.
π Installing Components #
First, add this package to your Flutter project.
flutter pub add rust_in_flutter
Then install Rust toolchain. Refer to the official Rust docs.
Finally, check that your system is ready for compiling. You can repeat these commands to verify your system status after each installation step. If there are no issues in the output, you are good to go!
rustc --version
flutter doctor
Caveats #
- For Android, The NDK version that your project expects is specified in
android/app/build.gradle
file asndkVersion
. This version number must be23.1.7779620
or higher for this package to work. If the value ofndkVersion
isflutter.ndkVersion
, you should be using Flutter SDK 3.10 or higher.
Using extra build targets with Rust can sometimes present various issues. If you encounter any problems, feel free to visit the discussions page and open a Q&A thread for assistance.
π Applying Template #
Simply run this in the command-line from the Flutter project folder.
dart run rust_in_flutter:apply_template
Once you've run the command, there will be some new files and folders that will be your starter Rust template.
my_flutter_project/
βββ android/
βββ ios/
βββ lib/
* β βββ main.dart
β βββ ...
βββ linux/
+ βββ native/
+ β βββ hub/
+ β β βββ src/
+ β β βββ Cargo.toml
+ β βββ sample_crate/
+ β β βββ src/
+ β β βββ Cargo.toml
+ β βββ README.md
βββ web/
βββ windows/
* βββ .gitignore
+ βββ Cargo.toml
* βββ pubspec.yaml
βββ ...
Don't forget to read the ./native/README.md
file first. Also, you might want to remove sample_crate
in production. If you already have a Rust crate that you want to use here, just put it inside ./native
and set it as a dependency of the hub
crate.
Now by heading over to ./native/hub/src/lib.rs
, you can start writing Rust!
𧱠Tips #
When requesting from Dart, you should specify the operation and address. This way of communication follows the definition of RESTful API.
import 'package:rust_in_flutter/rust_in_flutter.dart';
import 'package:msgpack_dart/msgpack_dart.dart';
void someFunction() async {
var rustRequest = RustRequest(
address: 'basicCategory.counterNumber',
operation: Operation.Read,
bytes: serialize(
{
'letter': 'Hello from Dart!',
'before_number': 888,
'dummy_one': 1,
'dummy_two': 2,
'dummy_three': [3, 4, 5]
},
),
);
var rustResponse = await requestToRust(rustRequest);
var message = deserialize(rustResponse.bytes) as Map;
var innerValue = message['after_number'] as int;
}
Upon receiving the request in Rust, you should first classify them by address.
pub async fn handle_request(request_unique: RustRequestUnique) -> RustResponseUnique {
let rust_request = request_unique.request;
let interaction_id = request_unique.id;
let layered: Vec<&str> = rust_request.address.split('.').collect();
let rust_response = if layered.is_empty() {
RustResponse::default()
} else if layered[0] == "basicCategory" {
if layered.len() == 1 {
RustResponse::default()
} else if layered[1] == "counterNumber" {
sample_functions::calculate_something(rust_request).await
} else {
RustResponse::default()
}
} else {
RustResponse::default()
};
RustResponseUnique {
id: interaction_id,
response: rust_response,
}
}
Endpoint function in Rust would be like this. Message schema is defined in the match statement because it will be different by the operation type.
pub async fn calculate_something(rust_request: RustRequest) -> RustResponse {
match rust_request.operation {
Operation::Create => RustResponse::default(),
Operation::Read => {
#[allow(dead_code)]
#[derive(Deserialize)]
struct RustRequestSchema {
letter: String,
before_number: i32,
dummy_one: i32,
dummy_two: i32,
dummy_three: Vec<i32>,
}
let slice = rust_request.bytes.as_slice();
let received: RustRequestSchema = from_slice(slice).unwrap();
println!("{:?}", received.letter);
let before_value = received.before_number;
let after_value = sample_crate::add_seven(before_value);
#[derive(Serialize)]
struct RustResponseSchema {
after_number: i32,
dummy_one: i32,
dummy_two: i32,
dummy_three: Vec<i32>,
}
RustResponse {
successful: true,
bytes: to_vec_named(&RustResponseSchema {
after_number: after_value,
dummy_one: 1,
dummy_two: 2,
dummy_three: vec![3, 4, 5],
})
.unwrap(),
}
}
Operation::Update => RustResponse::default(),
Operation::Delete => RustResponse::default(),
}
}
You can extend this RESTful API pattern and create hundreds and thousands of endpoints as you need. If you have a web background, this system might look familiar. More comments and details are included in the actual code inside the Rust template.
Ideally, Flutter would deal with the cross-platform user interface while Rust handles the business logic. The front-end and back-end can be completely separated, meaning that Dart and Rust codes are detachable from each other. These two worlds communicate through channels and streams.
Use MessagePack for serializing messages sent between Dart and Rust as provided by the Rust template unless you have other reasons not to do so. For those who aren't familiar, MessagePack is a nested binary structure similar to JSON, but faster and smaller.
Data being sent between Dart and Rust are basically bytes arrays, represented as Uint8List
in Dart and Vec<u8>
in Rust. Though using MessagePack serialization is recommended, you can send any kind of bytes data as you wish such as a high-resolution image or some kind of file data, or just toss in a blank bytes array if you don't need additional details.
β Support Us #
π If you are benefiting from the features of Rust-In-Flutter and find it helpful, why not consider supporting this project? Your generous donations contribute to the maintenance and development of Rust-In-Flutter, ensuring its continuous improvement and growth.
If you feel like so, please consider buying us a coffee.