flutter_rust_bridgeのResult型

2024/07/10に公開

はじめに

flutter_rust_bridgeでRustを呼び出す方法を勉強中です。
今回は、RustでResult型を返すとFlutter側ではどうなるか、を調べました。

  • Windows 11
    • Flutter 3.22.2
    • Dart 3.4.3
    • rustc 1.76.0

プロジェクト作成

cargo install flutter_rust_bridge_codegen && flutter_rust_bridge_codegen create my_app && cd my_app && flutter run

&& flutter runによりアプリ立ち上がりまで進みます

作成されたソースコードを編集していきます。
呼び出すRustのコードがResult型を返すように変更をしていきます。

FlutterにResult<T,E>を返す対応

Rustソース編集

rust\src\api\simple.rs

  • MyError型の作成
    • Display/Debug/Error traitを実装
  • greetの修正
    • 戻り値をcore::result::Result<T, MyError> に変更
    • 引数nameが空のときエラーを返すように変更
use std::fmt::Display;
#[derive(Debug)]
pub enum MyError{
    Empty,
}
impl Display for MyError{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self{
            MyError::Empty => f.write_str("Empty!!!"),
        }
    }
}

impl std::error::Error for MyError{}
pub type Result<T> = core::result::Result<T, MyError>;
#[flutter_rust_bridge::frb(sync)] // Synchronous mode for simplicity of the demo
pub fn greet(name: String) -> Result<String> {
    if !name.is_empty(){  
        Ok(format!("Hello, {name}!"))
    }else{
        Err(MyError::Empty)
    }
}

Dartコード自動生成

flutter_rust_bridge_codegen generate --watch

UIの変更

lib\main.dart

  • greetの引数nameを入力可能に書き換える
  • StatefulWidgetに変更
  • TextEditingControllerを配置しonSubmittedでgreetを呼び出して結果を表示
class MyApp extends StatefulWidget {
  const MyApp({super.key});

  
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  TextEditingController nameController = TextEditingController();
  String disptext = "";

  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('flutter_rust_bridge quickstart')),
        body: Column(
          children: [
            TextField(
              controller: nameController,
              onSubmitted: (name) {
                setState(() {
                  try {
                    disptext = greet(name: name);
                  } catch (e) {
                    disptext = e.toString();
                  }
                });
              },
            ),
            Center(
              child: Text(disptext),
            ),
          ],
        ),
      ),
    );
  }
}

fn greet(name:String)->Result<String>の実行

正常時は Hello, {name}!が表示されます。
エラー時は、コードのほうでネタバレしていますが、例外となります。flutter_rust_bridgeではResult::Errは例外に変換されます。

Arbitrary custom error type (fn f() -> Result<String, YourErrorType>): The YourErrorType will be automatically converted to a Dart exception.

試しに実行してみるとAnyhowException(Empty!!!)が表示されました。

Crate anyhowになるのですね

では、Rustと同じようにenum{Ok(T),Err(E)}にできるのか?
下記リンクに説明があります。
https://cjycode.com/flutter_rust_bridge/guides/types/translatable/detailed/enum

freezedというDart libraryで対応しているようです。こちらも試してみます。

Flutterにenum{Ok(T),Err(E)}を返す対応

freezed導入

flutter pub add freezed_annotation
flutter pub add dev:build_runner
flutter pub add dev:freezed
# if using freezed to generate fromJson/toJson, also add:
flutter pub add json_annotation
flutter pub add dev:json_serializable

Rust修正

  • enum CustomResultの作成
  • greetの戻り値をCustomResultに変更した関数を作成
pub enum CustomResult{
    Ok(String),
    Err(MyError),
}

#[flutter_rust_bridge::frb(sync)] // Synchronous mode for simplicity of the demo
pub fn greet2(name: String) -> CustomResult {
    if !name.is_empty(){  
        CustomResult::Ok(format!("Hello, {name}!"))
    }else{
        CustomResult::Err(MyError::Empty)
    }
}

UIの変更

lib\main.dartを変更

                setState(() {
                  greet2(name: name).when(
                    ok: (field0) => disptext2 = field0,
                    err: (field0) => disptext2 = field0.toString(),
                  );
                });

greet2のwhenは以下のように定義されています。

okのときとerrのときのクロージャをそれぞれ設定しているようですね。

TResult when<TResult extends Object?>({
  required TResult Function(String field0) ok,
  required TResult Function(MyError field0) err,
}) =>
    throw _privateConstructorUsedError;

fn greet2(name:String)->CustomResultの実行

正常時はgreetと同じです。
エラー時はMyError.emptyが表示されました。

Display traitの結果ではなく、型名が表示されているようですね

enumのマッチングのやり方がちょっと違いますが、雰囲気は同じですね。

あとがき

flutter_rust_bridgeを調べ始めた理由は、Rust Core 1.0というものがリリースされたという記事を見たのがきっかけです。
https://www.publickey1.jp/blog/24/rustdartrust_core_10rustdart.html

正直なところ、なぜこんなものを…というのが第一印象です。Result型も対応されていたりしますが、DartにRustっぽさが入ってくることになるのですが、どうなのでしょうか?
とはいえ興味はあるので今後触れたらいいなと思っています。

Discussion