Dart 및 Rust: 비동기식 스토리🔃
이 블로그 글에서 우리는 어떻게 다중 스레드 비동기 Rust 코드와 비동기 Dart를 결합하여 사용하는지, 그리고 여러 개의 실행할 때의 응용 프로그램을 구축하는지, 이런 실행할 때 sōzu처럼 원활하게 협동하여 작업할 수 있는지 토론할 것이다.
문제🎯
여러 개의 비동기 순환을 실행하는 것은 항상 정확하지 않습니다. Dart VM이 자신만의 비동기 운행을 가지고 있다는 것을 알고 있을 때,Rust에서 우리가 어떤 유행하는 운행을 삽입할 수 있을 때, 여기에는 Tokio, async-std을 참고할 수 있습니다. 최근 smol은 비동기 std에 내장되어 있습니다. 이것은 저급 운행이기 때문입니다.
문제는 Dart가 하나의 언어로서 단선으로 설계되었다는 것이다. 그 자체는 문제가 아니지만, 우리는 잠시 후에 Rust를 사용할 때 이것이 어떤 문제가 될지 보게 될 것이다.
좋습니다. Isolate여 개를 실행할 수 있습니다. 이것은 마치 소형 Dart 가상 기기의 실례와 같습니다. 그들은 함께 일하고 메시지 전달을 통해 통신할 수 있습니다. 다음은 Flatter 팀의 Andrew가 Dart 격리와 사건 순환에 대해 이야기하는 곳입니다. 만약에 Dart 격리를 모른다면 계속하기 전에 보는 것을 강력히 권장합니다(PS: 6분 미만).😅)
PC방 긁어달래요.🌍
이 글을 더욱 감동시키기 위해, Rust에 간단한 웹 캡처 라이브러리를 구축하고, 이를 Flatter 응용 프로그램에 사용합니다.
지난 블로그 글에서 두 언어를 어떻게 연결하는지에 대한 경험에 따라 우리는 여기서도 같은 생각을 사용할 것이다.
먼저 녹슨 판자 상자를 만듭니다.
$ cargo new --lib native/scrap # yes, it is a scrap lol
쿨, 이제 비동기 웹 요청을 보내는 방법이 필요해요. 그래서 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?)
}
보시다시피 이것은 간단한 코드로 대량의 오류 처리를 가지고 있습니다😂다음으로, 우리는 우리의 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와 잘 어울리기 때문에 우리는 그것을 사용할 것이다.이 밖에 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;
}
}
};
}
이것은 4개의 라인을 포함하는 Tokio
을 실행할 때 설정하고 각 라인을 flutterust
으로 명명합니다.오류 처리🧰
외국 금융 기구 간의 잘못을 처리하는 것은 어려운 것이기 때문에 우리는 조수 상자를 사용할 것이다. 이것은 우리의 생활을 더욱 쉽게 할 것이다.
의존 항목에 ffi_helpers 추가
ffi_helpers = "0.2"
이것은 우리가 오류를 처리하는 데 도움을 줄 수 있는 유용한 함수와 매크로를 제공합니다. last_error_length
과 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)
}
load\u 페이지 기능 공개🔑
이제 문제가 생겼습니다.
async
함수를 어떻게 공개할까요?응, 다트는 러스트의 async
이 어떻게 일하는지 몰라.한 가지 방법은 일반적인 비
async
함수를 async
함수로 하는 포장기를 작성한 다음에 block_on을 호출하여 async fn
에서 결과를 얻을 수 있습니다. 보시다시피 우리는 이 라인이 async
임무만 실행하는 것을 막을 것입니다. 그러면 우선 어떤 장점이 있습니까?또 다른 방법이 있습니다.
callbacks
을 사용하는 것이 어떻습니까?!좋습니다. 이것은 옵션입니다. 자바스크립트
Promise
스타일을 사용하여 두 개의 리셋을 전달하여 비동기적인 작업을 처리합니다. 하나는 success
, 다른 하나는 err
을 호출합니다.이론적으로는 가능하지만,
내가 전에 말한 바와 같이, Dart는 단일 스레드로 설계되었기 때문에, 너는 다른 스레드에서만 리셋을 호출할 수 없다. 그것은 기본적으로 Dart의 의미를 파괴할 것이다. (너는 더 많은 here을 읽을 수 있다.)
그럼 해결 방안은 무엇일까요?
만약 내가 너에게 말한다면, 우리는
Isolate
을 사용하여 다트와 러스트 사이에서 통신할 수 있을 것이다😦.새 격리 장치마다 SendPort과 ReceivePort이 있습니다.
Isolate
과 통신하는 데 사용됩니다. SendPort
에 Isolate
으로 메시지를 보내기만 하면 됩니다. SendPort
마다 NativePort이 있습니다. 이것은 이 포트의 기본 번호입니다.Just a small note, the term of
Port
here is not related at all to aNetworking
Port or something like that, it is just numbers used as Keys for a Map implemented internally in Dart VM, think of them as aHandle
to thatIsolate
만약 우리가 이 숫자를 사용하여 우리가 원하는 모든 라인에서
async fn
결과를 보낸다면, 이것은 가능합니까?맞다입력: Allo Isolate📞
allo-isolate은 여러 스레드 Rust 및 Dart VM(독립형)을 실행할 수 있도록 Sunshine에 구축된 케이스입니다.🌀
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
과 Android
을 위해 모든 내용을 구축한 후에 Dart단 FFI를 작성해야 합니다.아, 다시는 그러지 마세요.🤦♂️지금 C헤드 파일에서 Dart FFI 바인딩을 생성할 수 있다고 알려주면?
dart bindgen:dart FFI를 작성하는 새로운 방법
dart-bindgen은 Dart FFI를 C 헤더 파일에 연결하는 도구로 CLI과 Library으로 나뉜다.
우리는 그것을 사용하여 외국 금융 기구를 생성하는 데 도움을 줄 것입니다. 단지 그것을 당신의
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 Emulator 또는 iOS 에뮬레이터를 시작하고 다음을 실행합니다.$ cargo make # build all rust packages for iOS and Android
그리고$ flutter run # 🔥
모든 코드는 flutterust에서 사용할 수 있으며, 이것은 유일한 복제와 해커이다😀.Dart and Rust에 있는 다른 해커들을 계속 팔로우하세요. 저를 따라오시면 github을 클릭하세요.
정말 감사합니다.💚.
Reference
이 문제에 관하여(Dart 및 Rust: 비동기식 스토리🔃), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/sunshine-chain/rust-and-dart-the-async-story-3adk텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)