本文正在参加「金石计划 . 瓜分6万现金大奖」
官网:Introduction - flutter_rust_bridge (cjycode.com)
pub: flutter_rust_bridge | Dart Package (flutter-io.cn)
译时版本:1.49.1
原文链接:
- Language translations - flutter_rust_bridge (cjycode.com)
- Simple correspondence - flutter_rust_bridge (cjycode.com)
- Vec and array - flutter_rust_bridge (cjycode.com)
- Struct - flutter_rust_bridge (cjycode.com)
- Enum - flutter_rust_bridge (cjycode.com)
- External types - flutter_rust_bridge (cjycode.com)
- Option - flutter_rust_bridge (cjycode.com)
- Methods - flutter_rust_bridge (cjycode.com)
- Return types - flutter_rust_bridge (cjycode.com)
- Dynamic - flutter_rust_bridge (cjycode.com)
语言转译
该章节会展示各种语言特性如何在 Rust 和 Dart 之间转译。
简单类型对应
这里简要展示了代码生成器能生成什么代码(非复杂类型)。 一些行有超链接,指向更多详细的解释。
| Rust | Dart |
|---|---|
Vec<u8>, Vec<i8>.. | Uint8List, Int8List, .. |
Vec<T> | List<T> |
[T; N] | List<T> |
struct { .. }, struct( .. ) | class |
enum { A, B } | enum |
enum { A(..) } | @freezed class |
use ... | act normally |
Option<T> | T? |
Box<T> | T |
| comments | same |
Result::Err, panic | throw Exception |
i8, u8, .., usize | int |
f32, f64 | double |
bool | bool |
String | String |
() | void |
chrono crate 的类型是作为一个单独的特性。
uuid crate 的类型也是作为一个单独的特性。
Vec 和数组
Vec<u8>、 Vec<i8>、 ...
在Dart中,当要表达长字节的数组,如大的图像或一些二进制Blob,通常会使用 Uint8List 代替 List<int>,因为前者性能更好。flutter_rust_bridge 也考虑到了这一点。
当使用 Vec<u8> (或 Vec<i8>、 或 Vec<i32>、等)时,它会被翻译为 Uint8List 或相关类型。
Vec<T>
当使用用于 T 泛型的 Vec<T> 而不是使用用于 u8、 i8的 Vec 时,会被转换为 List<T> 。
[T; N]
因为 Dart 没有对应固定大小数组的处理,所以也会转换为 List<T> 。
示例
pub fn draw_tree(tree: Vec<TreeNode>) -> Vec<u8> { ... }
会转换为:
Future<Uint8List> drawTree({required List<TreeNode> tree});
注: 如果对于 Future 很感兴趣,可以看一下 这个 。
结构体
支持通常的 Rust 结构体。甚至可以使用递归字段,例如 pub struct TreeNode { pub value: String, pub children: Vec<MyTreeNode>, pub parent: Box<MyTreeNode> } 。
如果结构体的字段是结构体类型或枚举类型,需要在为其添加 Box ,否则会导致编译时错误。例如, struct A {b: B} 应该替换为 struct A {b: Box<B>} 。
元组结构体
元组结构体 struct Foo(A, B) 会转译为 class Foo { A field0; B field1; } ,因为 Dart 没有匿名字段。
非 final 字段
为结构体的字段添加 #[frb(non_final)] ,相应的 Dart 字段就会是非 final 的。
默认情况下,生成的所有字段都是 final 的,这里因为 Rust 的哲学 - 默认是不可变的。
Dart 元数据注解
可以在 frb 宏中使用 dart_metadata 参数添加 Dart 元数据注解。
-
注解在 Dart 中是前序,(例,
@deprecated) ,在 Rust 中当成文字即可。 -
如果需要导入,在注解客串后面添加导入的部分。现在支持两种形式的导入:
import 'somepackage'import 'somepackage' as somename,这里somename会是注解的前缀。
-
多个注解使用逗号
,分隔。
看一下下面的例子。
freezed Dart 类
如果想要生成 freezed (类似于其它语言如 Kotlin 的数据类) 的 Dart 类, 只需要添加 #[frb(dart_metadata=("freezed"))] ,它会为你生成所需的所有内容。
示例
示例1:递归字段
pub struct MyTreeNode {
pub value: Vec<u8>,
pub children: Vec<MyTreeNode>,
}
会转为:
class MyTreeNode {
final Uint8List value;
final List<MyTreeNode> children;
MyTreeNode({required this.value, required this.children});
}
示例2:元数据
#[frb(dart_metadata=("freezed", "immutable" import "package:meta/meta.dart" as meta))]
pub struct UserId {
pub value: u32,
}
会转为:
import 'package:meta/meta.dart' as meta;
@freezed
@meta.immutable
class UserId with _$UserId {
const factory UserId({
required int value,
}) = _UserId;
}
枚举
Rust 的 enum ,都知道很有表现力并且很强大 - 它允许每个枚举变量有不同关联的数据。
Dart 没有建这种枚举类型,但是不用担心 - 该库会转译为使用 freezed Dart 库的等价代码。
freezed 的语法,第一次看上去会觉着有些奇怪,但请看一下 它的文档 了解一下它的强大。
示例
pub enum KitchenSink {
Empty,
Primitives {
/// Dart 字段注释
int32: i32,
float64: f64,
boolean: bool,
},
Nested(Box<KitchenSink>),
Optional(
/// 匿名字段注释
Option<i32>,
Option<i32>,
),
Buffer(ZeroCopyBuffer<Vec<u8>>),
Enums(Weekdays),
}
为转为:
@freezed
class KitchenSink with _$KitchenSink {
/// 变量注释
const factory KitchenSink.empty() = Empty;
const factory KitchenSink.primitives({
/// Dart 字段注释
required int int32,
required double float64,
required bool boolean,
}) = Primitives;
const factory KitchenSink.nested(
KitchenSink field0,
) = Nested;
const factory KitchenSink.optional([
/// 匿名字段注释
int? field0,
int? field1,
]) = Optional;
const factory KitchenSink.buffer(
Uint8List field0,
) = Buffer;
const factory KitchenSink.enums(
Weekdays field0,
) = Enums;
}
它们可以使用 freezed 的 所有功能 。
外部类型
其它文件里有相同 crate 的类型
导入的标识符可正常使用。例如,使用 use crate::data::{MyEnum, MyStruct}; ,可以在代码中正常使用 MyEnum 或 MyStruct 。
示例
use crate::data::{MyEnum, MyStruct};
pub fn use_imported_things(my_struct: MyStruct, my_enum: MyEnum) { ... }
会转为:
// Well it just behaves normally as you expect
Future<void> useImportedThings({required MyStruct myStruct, required MyEnum myEnum});
其它 crate 中的类型
该特性称为 "镜像" 。简而言之,需要再次定义类型作为要使用的外部类型的镜像。该定义只在生成代码时使用,用来告知 flutter_rust_bridge 类型信息。要查看确切的语法,看一下下面的示例。
无需担心这是否会违反 DRY 原则,或者担心偶然写错字段时发生什么。 这里因为如果镜像类型和原来类型不完全相同的话,会 出现编译错误 。
更多信息:#352
多个结构体有相同的字段时,可以使用语法如 #[frb(mirror(FirstStruct, SecondStruct, ThirdStruct))] 只做 一次 镜像。 (#619)
示例
// 镜像示例:
// 镜像的目标是使用外部对象而无需将它们转换为中间类型。
// 该救命中,结构体 ApplicationSettings 定义在其它 crate (称为 外部库) 中。
// 要使用带镜像的外部类型,它 必须 是导入为 public 的 (也称作 再导出)。
pub use external_lib::{ApplicationEnv, ApplicationMode, ApplicationSettings};
// 要为外部结构体做镜像,需要用相同的定义定义一个占位类型
#[frb(mirror(ApplicationSettings))]
pub struct _ApplicationSettings {
pub name: String,
pub version: String,
pub mode: ApplicationMode,
pub env: Box<ApplicationEnv>,
}
// 基本的枚举也可如此使用
// 结构体变量的枚举现在还不支持
#[frb(mirror(ApplicationMode))]
pub enum _ApplicationMode {
Standalone,
Embedded,
}
#[frb(mirror(ApplicationEnv))]
pub struct _ApplicationEnv {
pub vars: Vec<String>,
}
// 该函数直接返回外部类型ApplicationSettings 的对象,因为它有一个镜像
pub fn get_app_settings() -> ApplicationSettings {
external_lib::get_app_settings()
}
// 类似地,从 Dart 端接收一个对象。请注意镜像定义必须完全匹配,并且原始的结构体的所有字段都必须是 public 的。
pub fn is_app_embedded(app_settings: ApplicationSettings) -> bool {
// println!("env: {}", app_settings.env.vars[0]);
match app_settings.mode {
ApplicationMode::Standalone => false,
ApplicationMode::Embedded => true,
}
}
另外一个使用一个结构体作为多个结构体镜像的示例:
// *不* 需要做这些
#[frb(mirror(MessageId))]
pub struct MId(pub [u8; 32]);
#[frb(mirror(BlobId))]
pub struct BId(pub [u8; 32]);
#[frb(mirror(FeedId))]
pub struct FId(pub [u8; 32]);
// 这样做即可
#[frb(mirror(MessageId, BlobId, FeedId))]
pub struct Id(pub [u8; 32]);
选项
对于可空变量,Dart有特有的语法 - ? 标识符, Option 会自动转译为 ? 。可以参考 官方文档 查看更多信息。
另外,flutter_rust_bridge 也能理解 Dart 中的 required 关键字:如果变量是非空的,就会被标记为 required ,因为你需要提供一个值。另一方面,如果它是可空的,就不需要 required ,因为在 Dart 会话中没有手动提供值就是空。
示例
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> { ... }
会转为:
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});
}
方法
支持结构体关联的方法。同时支持静态方法和非静态方法。
示例
pub struct SumWith { pub x: u32 }
impl SumWith {
pub fn sum(&self, y: u32) -> u32 { self.x + y }
pub fn sum_static(x: u32, y: u32) -> u32 { x + y }
}
会转为:
class SumWith {
final FlutterRustBridgeExampleSingleBlockTest bridge;
final int x;
SumWith({
required this.bridge,
required this.x,
});
Future<int> sum({required int y, dynamic hint}) => ..
static Future<int> sum({required int x, required int y, dynamic hint}) => ..
}
返回类型
返回类型可以是 anyhow::Result<YourType>,或直接是 YourType 。
示例
pub fn f(a: i32, b: i32) -> i32 { a + b }
pub fn g(a: i32, b: i32) -> anyhow::Result<i32> { Ok(a + b) }
动态类型
Dart 的 dynamic 类型是一种特殊的类型,它可以保有值的任意类型。
现在 flutter_rust_bridge 本身不支持这种类型,但是大多数场景,应该可以用 enum 代替要支持的每种类型的变量来解决。
示例
假设有一个结构体,它可以保有 u32 或 String 和其它字段(在明显的错误设计中):
struct MyStruct {
a: Optional<u32>,
b: Optional<String>,
}
struct DataStruct {
msg: String,
data: MyStruct,
}
可以在 Rust 中定义 enum (枚举)来代表:
enum MyEnum {
U32(u32),
String(String),
}
然后定义一个结构体保有这些 enum (枚举):
struct MyStruct {
msg: String,
data: MyEnum,
}
备注:如果对 Future 感兴趣,看一下 这里.
本文正在参加「金石计划 . 瓜分6万现金大奖」