ダーツとさび:async物語🔃


最後のブログ記事では、両方の言語を接続し、より安全な、パフォーマンスのフラッターアプリケーションを構築する方法を示します.
このブログ記事では、マルチスレッドAsync RustコードをAsync DARTで使用する方法と、1つ以上のランタイムを持つアプリケーションをどのように構築するかについて話しますsōzu 作品

問題🎯


Asyncのループを実行すると、常にそれを取得するのは簡単ではない、あなたはそれがDART VMが独自のAsyncのランタイムであり、錆では、我々は人気のランタイムのいずれかをプラグインすることができることを知っているTokio , async-std , 最近smol これは、低レベルのランタイムとして、async - stdを内蔵しています.
問題は、言語としてのDARTはシングルスレッドであるように設計されているということですが、それ自体は問題ではありませんが、後にどのようにRustを使うときに問題になるでしょう.
さて、あなたは以上を実行することができますIsolate これは、彼らが一緒に動作し、メッセージを渡すことによって通信する小さなダーツVMのインスタンスのようなものです、ここではフラッターチームからのアンドリューは、DARTの分離とイベントループについての話は、私は非常にダーツ分離についての考えがない場合は😅)

ウェブを掻きましょう🌍


このポストをよりエキサイティングにするために、単純なウェブをさびている図書館をこすって、フラッタアプリケーションでそれを使ってください.
両方の言語を接続する方法についての最後のブログの投稿からの経験を使用して、我々もここで同じ考えを使用します.
最初に錆木箱を作成します
$ cargo new --lib native/scrap # yes, it is a scrap lol
クールなので、我々は非同期のWebリクエストを送信する方法が必要ですので、使用してみましょうreqwest
[dependencies]
# we will use `rustls-tls` here since openssl is an issue when cross-compiling for Android/iOS
reqwest = { version = "0.10", default-features = false, features = ["rustls-tls"] }
さあさびたコードを書きましょう😀
// lib.rs
use std::{error, fmt, io};

/// A useless Error just for the Demo
#[derive(Copy, Clone, Debug)]
pub struct ScrapError;

impl fmt::Display for ScrapError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Error While Scrapping this page.")
    }
}

impl error::Error for ScrapError {}

impl From<reqwest::Error> for ScrapError {
    fn from(_: reqwest::Error) -> Self {
        Self
    }
}

impl From<io::Error> for ScrapError {
    fn from(_: io::Error) -> Self {
        Self
    }
}

/// Load a page and return its HTML body as a `String`
pub async fn load_page(url: &str) -> Result<String, ScrapError> {
    Ok(reqwest::get(url).await?.text().await?)
}

あなたが見るように、sh * tyエラー処理による若干の単純なコードです😂
次に、FFIバインディングを書き込みますscrap クレート
$ cargo new --lib native/scrap-ffi
我々の中でCargo.toml
[lib]
name = "scrap_ffi"
crate-type = ["cdylib", "staticlib"]

[dependencies]
scrap = { path = "../scrap" }
今、我々はここでいくつかのことを考慮する必要があるので、それは汚くなる

ランタイムの設定🔌


我々は選択しますTokio 私たちのランタイムとしては、どのようなランタイムを使用するかは問題ではありませんreqwest 私たちがそれを使うつもりであるtokioでよく働きます.
also lazy_static それは1秒で便利でしょう.
tokio = { version = "0.2", features = ["rt-threaded"] }
lazy_static = "1.4"
ランタイムを設定するコードを書きます
// lib.rs
use tokio::runtime::{Builder, Runtime};
use lazy_static::lazy_static;
use std::io;

lazy_static! {
    static ref RUNTIME: io::Result<Runtime> = Builder::new()
        .threaded_scheduler()
        .enable_all()
        .core_threads(4)
        .thread_name("flutterust")
        .build();
}

/// Simple Macro to help getting the value of the runtime.
macro_rules! runtime {
    () => {
        match RUNTIME.as_ref() {
            Ok(rt) => rt,
            Err(_) => {
                return 0;
            }
        }
    };
}
この設定Tokio 4スレッドのランタイムと命名flutterust 各スレッドに対して.

エラー処理🧰


ffiの間のエラーを処理するのは難しいので、我々は我々の人生を容易にするヘルパー枠を使用します.
追加ffi_helpers 依存する
ffi_helpers = "0.2"
これは、我々はエラーを処理するために役立つ可能性のある機能とマクロを提供し、公開last_error_length and error_message_utf8 それで、DARTの側では、読みやすいエラーメッセージを得ることができました.
macro_rules! error {
    ($result:expr) => {
        error!($result, 0);
    };
    ($result:expr, $error:expr) => {
        match $result {
            Ok(value) => value,
            Err(e) => {
                ffi_helpers::update_last_error(e);
                return $error;
            }
        }
    };
}

macro_rules! cstr {
    ($ptr:expr) => {
        cstr!($ptr, 0);
    };
    ($ptr:expr, $error:expr) => {{
        null_pointer_check!($ptr);
        error!(unsafe { CStr::from_ptr($ptr).to_str() }, $error)
    }};
}


#[no_mangle]
pub unsafe extern "C" fn last_error_length() -> i32 {
    ffi_helpers::error_handling::last_error_length()
}

#[no_mangle]
pub unsafe extern "C" fn error_message_utf8(buf: *mut raw::c_char, length: i32) -> i32 {
    ffi_helpers::error_handling::error_message_utf8(buf, length)
}

ページの機能を公開する🔑


今、問題は、どのように我々は露出するasync 機能?よくC/Dartはどのように錆のアイデアはありませんasync 作品

つの方法は、通常の非async のラッパとして機能するasync 関数と呼び出しblock_on 結果をasync fn , 関数名がすべて表示されているのを見ると、そのスレッドをブロックしてasync 仕事、第一に利益は何ですか?
もう一つの方法があります callbacks ?!
良い、それはオプションとJavaScriptを使用するPromise 2つのコールバックを渡して、asyncタスクを処理するスタイルsuccess そしてもう一つはerr .
理論的には動作しますが

前に言ったように、DARTはシングルスレッドであるように設計されているので、他のスレッドからコールバックを呼び出すことはできませんhere ).
では、解決策は何でしょうか?
私があなたに言ったならば、我々はDARTIsolate 😦.
すべての新しい分離SendPort and ReceivePort これらはIsolate そして、あなただけが必要SendPort メッセージを送信するIsolate , また、あらゆるSendPort 何かを持っているNativePort このポートの基になる数.

Just a small note, the term of Port here is not related at all to a Networking Port or something like that, it is just numbers used as Keys for a Map implemented internally in Dart VM, think of them as a Handle to that Isolate


我々がこれらの数を我々のものに使うならば、我々async fn 私たちが望むスレッドからの結果は、可能ですか?はい

分離する📞


allo-isolate 我々が築いた木箱ですSunshine DART VMと一緒にマルチスレッドの錆を実行するのを助けるために🌀
それは必要Port 閉じるこの動画はお気に入りから削除されています.
フードの下では、DART APIを使用しますDart_PostCObject 関数とほとんど変換することができますall ?) ラストタイプにラストタイプのdocs を参照してください.

Allo (pronounced Hello, without the H) usually used in communication over the phone in some languages like Arabic :).


私たちの例に戻ると、我々は今どのように公開する方法を知っているload_page 関数使用allo-isolate
allo-isolate = "0.1"
使用する
use allo_isolate::Isolate;

...

#[no_mangle]
pub extern "C" fn load_page(port: i64, url: *const raw::c_char) -> i32 {
    // get a ref to the runtime
    let rt = runtime!();
    let url = cstr!(url);
    rt.spawn(async move {
        // load the page and get the result back
        let result = scrap::load_page(url).await;
        // make a ref to an isolate using it's port
        let isolate = Isolate::new(port);
        // and sent it the `Rust's` result
        // no need to convert anything :)
        isolate.post(result);
    });
    1
}
偉大な、我々は行く準備が整いましたbinding.h そして、すべての建物iOS and Android 我々はまた、ダーツ側FFIを書く必要があります.UGH再び🤦‍♂️

もし私があなたがCヘッダーファイルからあなたのダーツFCI結合を生成することができたと言うならば、どうですか?

DART BINDGEN:DART FFIを書く新しい方法


dart-bindgen Cヘッダーファイルにダーツffiバインディングを生成するためのツールですCLILibrary .
そして、我々はFFIを生成するためにそれを使用するには、ちょうどあなたのbuild-dependencies
[build-dependencies]
cbindgen = "0.14.2" # Rust -> C header file
dart-bindgen = "0.1" # C header file -> Dart FFI
そして、我々のbuild.rs
use dart_bindgen::{config::*, Codegen};

fn main() {
    ...
    let config = DynamicLibraryConfig {
        ios: DynamicLibraryCreationMode::Executable.into(),
        android: DynamicLibraryCreationMode::open("libscrap_ffi.so").into(),
        ..Default::default()
    };
    // load the c header file, with config and lib name
    let codegen = Codegen::builder()
        .with_src_header("binding.h")
        .with_lib_name("libscrap")
        .with_config(config)
        .build()
        .unwrap();
    // generate the dart code and get the bindings back
    let bindings = codegen.generate().unwrap();
    // write the bindings to your dart package
    // and start using it to write your own high level abstraction.
    bindings
        .write_to_file("../../packages/scrap_ffi/lib/ffi.dart")
        .unwrap();
}
これまでのところ、我々はより高いレベルの抽象化を書きましょうffi.dart ファイル.
// packages/scrap_ffi/lib/scrap.dart

import 'dart:async';
import 'dart:ffi';
import 'package:ffi/ffi.dart';
// isolate package help us creating isolate and getting the port back easily.
import 'package:isolate/ports.dart';

import 'ffi.dart' as native;

class Scrap {
  // this only should be called once at the start up.
  static setup() {
    // give rust `allo-isolate` package a ref to the `NativeApi.postCObject` function.
    native.store_dart_post_cobject(NativeApi.postCObject);
    print("Scrap Setup Done");
  }

  Future<String> loadPage(String url) {
    var urlPointer = Utf8.toUtf8(url);
    final completer = Completer<String>();
    // Create a SendPort that accepts only one message.
    final sendPort = singleCompletePort(completer);
    final res = native.load_page(
      sendPort.nativePort,
      urlPointer,
    );
    if (res != 1) {
      _throwError();
    }
    return completer.future;
  }

  void _throwError() {
    final length = native.last_error_length();
    final Pointer<Utf8> message = allocate(count: length);
    native.error_message_utf8(message, length);
    final error = Utf8.fromUtf8(message);
    print(error);
    throw error;
  }
}
フラッタアプリケーションでは、以下のようにパッケージを使用できます.
...
class _MyHomePageState extends State<MyHomePage> {

  ...
  Scrap scrap;
  @override
  void initState() {
    super.initState();
    scrap = Scrap();
    Scrap.setup();
  }  

  // somewhere in your app
  final html = await scrap.loadPage('https://www.rust-lang.org/');
それは、AndroidのエミュレータやIOSのシミュレータを起動し、実行する:
$ cargo make # build all rust packages for iOS and Android
Then,
$ flutter run # 🔥
すべてのコードはflutterust クローンとハックだけです😀.
ダーツとさびの他のハックのために調整されて、あなたは私に続くことができました、そしてgithub
ありがとう💚.