[官文翻译]绑定Flutter和Rust高级的内存安全代码生成器 flutter_rust_bridge - 特性(一) - 语言转译

617 阅读6分钟

本文正在参加「金石计划 . 瓜分6万现金大奖」


官网:Introduction - flutter_rust_bridge (cjycode.com)

pub: flutter_rust_bridge | Dart Package (flutter-io.cn)

译时版本:1.49.1

原文链接:


语言转译

该章节会展示各种语言特性如何在 Rust 和 Dart 之间转译。

简单类型对应

这里简要展示了代码生成器能生成什么代码(非复杂类型)。 一些行有超链接,指向更多详细的解释。

RustDart
Vec<u8>Vec<i8>..Uint8ListInt8List, ..
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
commentssame
Result::Err, panicthrow Exception
i8u8, .., usizeint
f32f64double
boolbool
StringString
()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万现金大奖」