Skip to content

Commit

Permalink
add examples to documentation; especially for Stream (the Stream part…
Browse files Browse the repository at this point in the history
… relates to #347)
  • Loading branch information
fzyzcjy committed Feb 18, 2022
1 parent 863c7a4 commit b4d7501
Show file tree
Hide file tree
Showing 10 changed files with 335 additions and 10 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## Unreleased

* Add examples to documentation

## 1.19.2

* Avoid converting syn types to strings before parsing #346 (thanks @antonok-edm)
Expand Down
7 changes: 4 additions & 3 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
- [🎼 Features](feature.md)
- [Language translations](feature/lang.md)
- [Vec](feature/lang_vec.md)
- [struct](feature/lang_struct.md)
- [enum](feature/lang_enum.md)
- [use](feature/lang_use.md)
- [Struct](feature/lang_struct.md)
- [Enum](feature/lang_enum.md)
- [Use](feature/lang_use.md)
- [Option](feature/lang_option.md)
- [Zero copy](feature/zero_copy.md)
- [Stream / Iterator](feature/stream.md)
- [Async in Dart](feature/async_dart.md)
Expand Down
2 changes: 1 addition & 1 deletion book/src/feature/lang.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Here is a brief glance showing what the code generator can generate (non-exhaust
| [`enum { A, B }`](lang_enum.md) | `enum` |
| [`enum { A(..) }`](lang_enum.md) | `@freezed class` |
| [`use ...`](lang_use.md) | act normally |
| `Option<T>` | `T?` |
| [`Option<T>`](lang_option.md) | `T?` |
| `Box<T>` | `T` |
| comments | same |
| `Result::Err`, panic | `throw Exception` |
Expand Down
59 changes: 58 additions & 1 deletion book/src/feature/lang_enum.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,60 @@
# `enum`s

Rust's `enum` are known to be very expressive and powerful - it allows each enum variant to have different associated data. Dart does not have such things in built-in enums, but no worries - we will automatically translate it into the equivalent using the `freezed` Dart library.
Rust's `enum` are known to be very expressive and powerful - it allows each enum variant to have different associated data. Dart does not have such things in built-in enums, but no worries - we will automatically translate it into the equivalent using the `freezed` Dart library. The syntax for `freezed` may look a bit strange at the first glance, but please look at [its doc](https://pub.dev/packages/freezed) and see its powerfulness.

## Example

```rust,noplayground
pub enum KitchenSink {
Empty,
Primitives {
/// Dart field comment
int32: i32,
float64: f64,
boolean: bool,
},
Nested(Box<KitchenSink>),
Optional(
/// Comment on anonymous field
Option<i32>,
Option<i32>,
),
Buffer(ZeroCopyBuffer<Vec<u8>>),
Enums(Weekdays),
}
```

Becomes:

```Dart
@freezed
class KitchenSink with _$KitchenSink {
/// Comment on variant
const factory KitchenSink.empty() = Empty;
const factory KitchenSink.primitives({
/// Dart field comment
required int int32,
required double float64,
required bool boolean,
}) = Primitives;
const factory KitchenSink.nested(
KitchenSink field0,
) = Nested;
const factory KitchenSink.optional([
/// Comment on anonymous field
int? field0,
int? field1,
]) = Optional;
const factory KitchenSink.buffer(
Uint8List field0,
) = Buffer;
const factory KitchenSink.enums(
Weekdays field0,
) = Enums;
}
```

And they are powered with [all functionalities](https://pub.dev/packages/freezed) of `freezed`.

Remark: If you are curious about `Future`, have a look at [this](async_dart.md).

35 changes: 35 additions & 0 deletions book/src/feature/lang_option.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# `Option`s

Dart has special syntaxs for nullable variables - the `?` symbol, and we translate `Option` into `?` automatically. You may refer to [the official doc](https://dart.dev/null-safety) for more information.

In addition, `flutter_rust_bridge` also understands the `required` keyword in Dart: If an argument is not-null, it is marked as `required` since you have to provide a value. On the other hand, if it is nullable, no `required` is needed since by Dart's convention a null is there in absence of manually providing a value.

## Example

```rust,noplayground
pub struct Element {
pub tag: Option<String>,
pub text: Option<String>,
pub attributes: Option<Vec<Attribute>>,
pub children: Option<Vec<Element>>,
}
pub fn parse(mode: String, document: Option<String>) -> Option<Element> { ... }
```

Becomes:

```Dart
Future<Element?> handleOptionalStruct({required String mode, String? document});
class Element {
final String? tag;
final String? text;
final List<Attribute>? attributes;
final List<Element>? children;
Element({this.tag, this.text, this.attributes, this.children});
}
```

Remark: If you are curious about `Future`, have a look at [this](async_dart.md).

24 changes: 23 additions & 1 deletion book/src/feature/lang_struct.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,26 @@

You can even use recursive fields. For example: `pub struct TreeNode { pub value: String, pub children: Vec<MyTreeNode>, pub parent: Box<MyTreeNode> }`.

Tuple structs `struct Foo(A, B)` are translated as `class Foo { A field0; B field1; }`, since Dart does not have anonymous fields.
Tuple structs `struct Foo(A, B)` are translated as `class Foo { A field0; B field1; }`, since Dart does not have anonymous fields.

## Example

```rust,noplayground
pub struct MyTreeNode {
pub value: Vec<u8>,
pub children: Vec<MyTreeNode>,
}
```

Becomes:

```Dart
class MyTreeNode {
final Uint8List value;
final List<MyTreeNode> children;
MyTreeNode({required this.value, required this.children});
}
```

Remark: If you are curious about `Future`, have a look at [this](async_dart.md).

20 changes: 19 additions & 1 deletion book/src/feature/lang_use.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
# `use`

Imported symbols can be used normally. For example, with `use crate::data::{MyEnum, MyStruct};`, you can use `MyEnum` or `MyStruct` in your code normally.
Imported symbols can be used normally. For example, with `use crate::data::{MyEnum, MyStruct};`, you can use `MyEnum` or `MyStruct` in your code normally.

## Example

```rust,noplayground
use crate::data::{MyEnum, MyStruct};
pub fn use_imported_things(my_struct: MyStruct, my_enum: MyEnum) { ... }
```

Becomes:

```Dart
// Well it just behaves normally as you expect
Future<void> useImportedThings({required MyStruct myStruct, required MyEnum myEnum});
```

Remark: If you are curious about `Future`, have a look at [this](async_dart.md).

17 changes: 16 additions & 1 deletion book/src/feature/lang_vec.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
# `Vec<u8>` and `Vec<T>`

In Dart, when you want to express a long byte array such as a big image or some binary blob, people normally use `Uint8List` instead of `List<int>` since the former is much performant. `flutter_rust_bridge` takes this into consideration for you. When you have `Vec<u8>` (or `Vec<i8>`, or `Vec<i32>`, etc), it will be translated it into `Uint8List` or its friends; but when you have normal `Vec<T>` for other `T` types, it will be normal `List<T>`.
In Dart, when you want to express a long byte array such as a big image or some binary blob, people normally use `Uint8List` instead of `List<int>` since the former is much performant. `flutter_rust_bridge` takes this into consideration for you. When you have `Vec<u8>` (or `Vec<i8>`, or `Vec<i32>`, etc), it will be translated it into `Uint8List` or its friends; but when you have normal `Vec<T>` for other `T` types, it will be normal `List<T>`.

## Example

```rust,noplayground
pub fn draw_tree(tree: Vec<TreeNode>) -> Vec<u8> { ... }
```

Becomes:

```Dart
Future<Uint8List> drawTree({required List<TreeNode> tree});
```

Remark: If you are curious about `Future`, have a look at [this](async_dart.md).

159 changes: 158 additions & 1 deletion book/src/feature/stream.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,161 @@ Flutter's [Stream](https://dart.dev/tutorials/language/streams) is a powerful ab

For example, your Rust function may run computationally heavy algorithms, and for every hundreds of milliseconds, it finds out a new piece of the full solution. In this case, it can immediately give that piece to Flutter, then Flutter can render it to UI immediately. Therefore, users do not need to wait for the full algorithm to finish before he can see some partial results on the user interface.

As for the details, a Rust function with signature like `fn f(sink: StreamSink<T>, ..) -> Result<()>` is translated to a Dart function `Stream<T> f(..)`.
As for the details, a Rust function with signature like `fn f(sink: StreamSink<T>, ..) -> Result<()>` is translated to a Dart function `Stream<T> f(..)`.

Notice that, you can hold that `StreamSink` forever, and use it freely even *after the Rust function itself returns*. The logger example below also demonstrates this (the `create_log_stream` returns almost immediately, while you can use the `StreamSink` after, say, an hour).

## Examples

The following examples only serve to deepen your understanding for this `Stream` feature.

### Example: Logger

Let us implement a simple logging system (adapted from the logging system I use with `flutter_rust_bridge` in my app in production), where Rust code can send logs to Dart code.

The Rust `api.rs`:

```rust,noplayground
pub struct LogEntry {
pub time_millis: i64,
pub level: i32,
pub tag: String,
pub msg: String,
}
// Simplified just for demonstration. To compile, you need a Mutex or RwLock or something like that
lazy_static! { static ref log_stream_sink: StreamSink<LogEntry>; }
pub fn create_log_stream(s: StreamSink<LogEntry>) {
stream_sink = s;
}
```

Generated Dart code:

```Dart
Stream<LogEntry> createLogStream();
```

Now let us use it in Dart:

```dart
Future<void> setup() async {
createLogStream().listen((event) {
print('log from rust: ${event.level} ${event.tag} ${event.msg} ${event.timeMillis}');
});
}
```

And now we can happily log anything in Rust:

```rust,noplayground
log_stream_sink.add(LogEntry { msg: "hello I am a log from Rust", ... })
```

Of course, you can implement a logger following the Rust's `log` crate wrapping this raw stream sink, then you can use standard Rust logging mechanisms like `info!`. I exactly did that in my project.

### Example: Simple timer

Credits: https://gist.github.com/Desdaemon/be5da0a1c6b4724f20093ef434959744 and https://github.com/fzyzcjy/flutter_rust_bridge/issues/347

```rust,noplayground
use anyhow::Result;
use std::{thread::sleep, time::Duration};
use flutter_rust_bridge::StreamSink;
const ONE_SECOND: Duration = Duration::from_secs(1);
// can't omit the return type yet, this is a bug
pub fn tick(sink: StreamSink<i32>) -> Result<()> {
let mut ticks = 0;
loop {
sink.add(ticks);
sleep(ONE_SECOND);
if ticks == i32::MAX {
break;
}
ticks += 1;
}
Ok(())
}
```

And use it in Dart:

```dart
import 'package:flutter/material.dart';
import 'ffi.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late Stream<int> ticks;
@override
void initState() {
super.initState();
ticks = api.tick();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text("Time since starting Rust stream"),
StreamBuilder<int>(
stream: ticks,
builder: (context, snap) {
final style = Theme.of(context).textTheme.headline4;
final error = snap.error;
if (error != null)
return Tooltip(
message: error.toString(),
child: Text('Error', style: style));
final data = snap.data;
if (data != null) return Text('$data second(s)', style: style);
return const CircularProgressIndicator();
},
)
],
),
),
);
}
}
```

18 changes: 17 additions & 1 deletion book/src/feature/zero_copy.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
# Zero copy

`ZeroCopyBuffer<Vec<u8>>` (and its friends like `ZeroCopyBuffer<Vec<i8>>`) sends the data from Rust to Dart without making copies. Thus, you save the time of copying data, which can be large if your data is big (such as a high-resolution image).
`ZeroCopyBuffer<Vec<u8>>` (and its friends like `ZeroCopyBuffer<Vec<i8>>`) sends the data from Rust to Dart without making copies. Thus, you save the time of copying data, which can be large if your data is big (such as a high-resolution image).

## Example

```rust,noplayground
pub fn draw_tree(tree: Vec<TreeNode>) -> ZeroCopyBuffer<Vec<u8>> { ... }
```

Becomes:

```Dart
Future<Uint8List> drawTree({required List<TreeNode> tree});
```

The generated Dart code looks exactly the same as the case without `ZeroCopyBuffer`. However, the internal implementation changes and there is no memory copy at all!

Remark: If you are curious about `Future`, have a look at [this](async_dart.md).

0 comments on commit b4d7501

Please sign in to comment.