Dioxus 0.5:Rustで開発するWeb、デスクトップ、モバイルアプリ
(dioxuslabs.com)- Rust GUIフレームワーク Dioxus 0.5 は、
dioxus-coreの書き直しと unsafe の排除を中心に、Web・デスクトップ・モバイル・Fullstack 開発の流れを大きく簡素化 - 0.4.3 から 0.5.0 までの間に 10万行以上 のコードと1,400件以上のコミットが入り、新しいコアはライフタイムと
Scopeへの依存をなくす方向に変更 - 従来の
use_state・use_ref中心の状態管理は Copy 可能なSignalAPIに置き換えられ、イベントハンドラや future で繰り返しCloneする負担が軽減 - 単一の
dioxus::launchとdx serve --platformの流れで Web、デスクトップ、Fullstack アプリを実行し、CLI が対象プラットフォームに合った ビルド feature を自動で渡す - アセットのホットリロード、イベントの再設計、デスクトップレンダリング改善、サーバー関数ストリーミングもあわせて変更され、単一の Rust コードベースの適用範囲が拡大
Dioxus 0.5のリリース方針
- DioxusはRustでGUIを作るライブラリであり、Webアプリ・デスクトップアプリ・モバイルアプリの配布に使われている
- 0.5リリースは、コミュニティが求めた 簡素化、堅牢性、完成度向上を目標に設計された
- 主な変更点は以下の通り
dioxus-coreの完全な書き直しと unsafe コードの排除use_state、use_refの代わりにSignalベースのAPIを導入- すべてのライフタイムと
cx: Scope状態を削除 - すべてのプラットフォームでアプリを開始する単一の
launch関数 - TailwindとVanilla CSSをサポートするアセットのホットリロード
- ネイティブ
WebSysイベント型へのアクセスを許可するイベントの再設計 - Error Boundary、Server Future、Suspense の統合
- デスクトップ reconciliation を5倍改善
- サーバー関数ストリーミングとFullstackホットリロード
- Dioxus 0.4から更新するユーザー向けに migration guide が提供されている
ライフタイムとScopeの削除
- Dioxus 0.1から0.4までは、コンポーネント内部の値が
'bumpライフタイムを持ち、これによりイベントリスナーでhook、props、scopeを複製なしで使えた - イベントハンドラではおおむねうまく動作していたが、Dioxusのfutureは
'staticである必要があり、値をfuture内へ移動する前にcloneが必要だった - ライフタイムエラーが発生すると、hook自体ではなく
cxが'staticより長く生存する必要がある、といったメッセージが出て混乱を招くことがあった - Dioxus 0.5は
Scopeと'bumpライフタイムを削除し、Elementをライフタイムのない形に変更 - コンポーネントは今後、scopeパラメータなしでpropsを直接受け取れる
- 例:
fn MyComponent(name: String) -> Element
- 例:
- ランタイム関数はコンポーネント内部だけでなく、futureやイベントハンドラ内部でも直接使用できる
Elementが'staticになったことで、hook内で使ったりcontext APIを通じて提供したりすることも可能に- この変更は、仮想リストやオフスクリーンレンダリングのようなAPIを実装しやすくする基盤になる
dioxus-core のunsafe排除
'bumpライフタイムとscopeの削除は、Dioxus内部のunsafeコードを減らす機会を作ったdioxus-core 0.5にはunsafeコードがない- 一部の依存関係には少量のunsafeが残っており、Dioxusチームは0.5リリースサイクル中にこれを取り除く計画
- 残るunsafeは、単に削除可能なもの、またはFFIのために必要なものに分類される
Signalベースの状態管理
- Dioxus 0.5は、コンポーネントの中核的な状態プリミティブとして Signal を導入
- Signalは従来の
use_state、use_refに比べて2つの利点を持つ- 常に
Copy可能 - 手動購読が不要
- 常に
-
Copy状態
Signal<T>は内部のT値がCopyでなくてもCopy- この動作は、unsafeなしで実装された generational-box crate によって可能になった
- 必要であればSignalを
Send+Syncにでき、スレッド間で移動できる - Copy状態、
Send+SyncSignal、staticコンポーネントの組み合わせにより、状態をfuture、イベントハンドラ、スレッドなど必要な場所へ簡単に移動できる - メモリ使用方式は0.4とほぼ同じだが、明示的な
Cloneは不要
-
スマート購読
- Signalは値が変わったとき、どのコンポーネントを再実行するかをより細かく判断する
- コンポーネントがSignal値を読んだ場合にのみ、そのコンポーネントが再実行される
- async処理やイベントハンドラで読んだ場合は、コンポーネント再実行の購読として扱われない
- 親がボタンクリックでSignalを変更するが値を直接読まず、子だけが値を読む場合、子だけが再レンダリングされる
- この構造により、別の状態管理crateだった Fermi が不要になった
- Fermiはstaticをキーに使う
use_stateに似たAPIを提供していた - Dioxus 0.5では
GlobalSignalをstaticに置き、通常のSignalのように使える - Signalはcontext APIとも動作し、別途
use_shared_statehookなしでコンポーネント間の状態共有が可能 use_future、use_memoのようなhook内でSignalを読むと、そのSignalがhook依存関係に自動追加される
CSSとアセットのホットリロード
- Dioxus 0.5は、アセットシステム刷新の一部としてアセットディレクトリ内のCSSファイルのホットリロードを実装
- CSSファイルがRSX内に現れると、
dxCLIがそのファイルを監視し、実行中のアプリへ更新を即座にストリーミングする - 対応対象はWeb、Desktop、Fullstackで、モバイル対応は今後のモバイル中心アップデートで提供予定
- Tailwind watcherと併用すると Tailwind CSSホットリロード もサポートされる
- VSCodeでは custom regex extension によりTailwind classのヒントも受けられる
- 複数デバイスに変更を同時にストリーミングし、対象デバイス全体でホットリロードできる
イベントシステムの再設計
- Dioxusはリリース以来、クロスプラットフォームイベントAPIを作るためにsynthetic eventシステムを使ってきた
- Synthetic eventはプラットフォーム間のイベント動作とネットワークシリアライズに有用だが、限界もあった
- Dioxus 0.5は各プラットフォームの 基盤イベント型 を公開し、クロスプラットフォームAPI向けのtraitもあわせて提供する
- この変更の利点は2つ
- プラットフォームイベント型から必要な情報を直接得たり、ほかのライブラリへ渡したりできる
- アプリが使わないイベントコードをbundle splittingできる
- hello worldの例でgzippedサイズが約 25%減少
- 小さなbundleを作るコツは Dioxus optimization guide に含まれている
クロスプラットフォーム実行API
- Dioxus 0.5は、アプリ実行のための新しい クロスプラットフォームAPI を導入
- 別のrendererパッケージを取り込む代わりに、
dioxuscrateでfeatureを有効化し、preludeのlaunch関数を呼べばよい - 1つのアプリから次のプラットフォームを対象に実行できる
- Desktop:
dx serve --platform desktop - SPA Web:
dx serve --platform web - Fullstack:
dx serve --platform fullstack
- Desktop:
- CLIは対象プラットフォームに応じて適切なビルドfeatureを自動的に渡す
アセットシステムβとManganis
- DioxusとWebアプリでは、アセットパスが古くなりやすく、デスクトップとWebでリンクが異なる場合があり、bundleに含めるアセットを手動で追加しなければならない問題がある
- アセットは性能上のボトルネックにもなり得る
- Dioxus Mobileガイドの例では、0.4版は読み込みに7秒かかり、9MBのリソースを転送していた
- 0.5のモバイルガイドは同じ画像を使いながらも1秒未満でロードされ、必要なリソースは1/3に減った
- Dioxus 0.5は新しいアセットシステム manganis を導入
- CLIと統合され、アプリアセットを確認、バンドル、最適化する
- APIがまだ不安定なため、別crateとして配布される
mg!マクロでアセットを包むとCLIが自動検出する- 詳細は manganis docs にある
- 0.5リリースが進む間に、manganisアセットにもホットリロードを追加する計画
デスクトップレンダリングを5倍改善
- Dioxusはレンダリングdiffを高速化するために複数の最適化を使う
- Templates は
rsx!マクロの静的部分のdiffをスキップさせる - Dioxus Webでは sledgehammer により、RustからDOM変更を高速に適用する
- Dioxus 0.5は同じ方式をネットワーク経由の変更適用にも使う
- DesktopとLiveView rendererは変更内容をJSONではなく バイナリプロトコル で通信する
- レンダリング負荷の高い作業で、新rendererはブラウザに変更を適用する時間が1/5に減り、レイテンシは1/2に減った
- Dioxus 0.4でrendererが止まり続けていたベンチマークが、Dioxus 0.5では滑らかに動作する
コンポーネント作成の便利機能
- Dioxus 0.5は特定のelementを拡張し、属性をelementへ展開する機能をサポート
- 例:
imgelementの属性を拡張したImgPlusコンポーネントで、width、height、srcのような通常のimg属性をそのまま受け取れる
- 例:
- 属性とコンポーネントに値を渡す際、構造体初期化の省略構文を使える
class: classの代わりにclassのように書ける
- 省略属性は
IntoAttributeを実装するすべての項目で動作し、Signalもこの恩恵を受ける - Signal属性はまだdiffingをスキップしないが、0.5リリースサイクル中に性能最適化として追加される予定
- 複数行に分かれた属性はマージできる
- 同じ
class属性に条件付きの値を追加すると、空白を区切りとしてマージされる - Tailwindのようにコンパイル時解析が必要で、かつランタイム動的処理も必要なライブラリに重要
- この構文はTailwind compilerと統合され、
tailwind-mergeのようなライブラリのランタイムオーバーヘッドを取り除く
- 同じ
サーバー関数ストリーミングとFullstack
- Dioxus 0.5はストリーミングデータをサポートする最新の server functions crate をサポート
- サーバー関数はクライアントへデータをストリーミングしたり、クライアントからサーバーへデータをストリーミングしたりできる
- ストリーミングサーバー関数は、出力型を定義し、サーバー関数から
TextStreamを返す方式で作れる - 時間のかかる作業中にクライアントを更新するのに適している
- KalosmとローカルLLMを使い、一般的なハードウェアでOpenAI ChatGPT endpointに似た機能を提供する例がある
- CLIは現在
fullstackプラットフォームをサポートし、クライアントとサーバーのホットリロードおよび並列ビルドを提供するdx servedx serve --platform fullstack
LiveView、アセットハンドラ、ファイル処理
- Dioxus 0.5ではrouterがLiveViewアプリでデフォルト動作する
- Dioxus Desktopはcustom asset handlerをサポート
- Custom asset handlerにより、RustコードからJavaScriptを経由せずブラウザへデータを効率的にストリーミングできる
- 動画ストリーミングのような帯域幅の大きい通信に適している
- gstreamerやwebrtcのデータを直接webviewへ渡せるため、フレームを直接エンコード・デコードする必要を減らせる
- デスクトップのファイルドロップもイベントシステムへネイティブに統合される
エラー処理
- DioxusはError Boundaryと
throwtraitにより、上位コンポーネントでエラーを簡単に処理できるようにする throw方式はエラー状態と早期returnの利点を組み合わせるDebugを実装するResult型でthrowを呼んでエラー状態に変え、?で早期returnできるErrorBoundaryコンポーネントは、子で投げられたエラーがあると別のコンポーネントをレンダリングするErrorBoundaryはネスト可能で、アプリの複数レベルでエラーを捕捉できる- このパターンは、復旧不能なエラーが発生したときにpanicしたりエラーごとに状態を直接管理したりせず、グローバルなエラー状態を扱うのに有用
開発体験とテンプレート
- Dioxusは0.3でホットリロードを導入し、0.4でDesktopに追加し、0.5ではデフォルトで有効化した
dx serveでアプリを実行すると、開発モードではデフォルトで ホットリロード が有効になる- Desktopアプリでホットリロードできず全体の再コンパイルが必要な場合でも、開いているウィンドウの状態を保持して復元する
- アプリウィンドウのサイズと位置が維持される
- 編集のたびにアプリが画面全体を塞ぐ状況を減らす
- 新しいテンプレートは、Web、Desktop、Mobile、TUI、Fullstackアプリを1つのコマンドで作れるよう整理された
dx newのデフォルトアプリはcreate-react-appに近い形へ変更- assets、CSS、基本的なデプロイ設定を含む
- dioxus-std、VSCode Extension、ドキュメント、チュートリアルなど有用なリソースへのリンクを含む
Dioxus Communityとエコシステム
- Dioxus Communityは0.5リリースに向けて重要なエコシステムcrateを更新
- icons、charts、Dioxus専用標準ライブラリのようなcrateが、0.5リリース時点ですぐ使えるよう準備された
Dioxus Communityプロジェクトは、元のmaintainerが退いた後も重要なcrateを最新状態に保つための新しいGitHub組織- Dioxus向けライブラリを作る場合、Dioxus側はその保守を支援でき、事実上「Tier 2」サポートとして維持することを目指す
今後予定されている機能
- 0.5以降の計画には次の項目が含まれる
- アセットシステムの安定化とより深い統合
- lazy componentとともに出力
.wasmを直接bundle splitting - Islandsとresumable interactivity、Signalシリアライズ
- Server componentとLiveViewをFullstackへ統合
- 強化されたDevtoolsとテストフレームワーク
- Mobile全体の刷新
- WebSocket、SSE、progressive formなどを含むFullstack刷新
ServoベースのDioxus-Blitzプレビュー
- Dioxus-Blitzの「Blitz 2.0」ではServoを統合し、Firefoxを動かしているものと同じCSSエンジンでWGPUネイティブレンダリングを目指す
- Taffy layout libraryを作ったNico Burnsが、この作業を推進するためにフルタイムで参加
- デモでは
google.comをGPU上で900 FPSでレンダリング - 現在の実装はまだ完全ではなく、
google.comのレンダリングもやや不自然だが、利用可能な水準へ急速に近づいている - リポジトリは https://github.com/jkelleyrtp/stylo-dioxus で確認できる
貢献方法
- Dioxusプロジェクトは次の貢献を求めている
- ドキュメント翻訳
- “Good First Issues”への挑戦
- ドキュメント改善
- CLIへの貢献
- Discordコミュニティでの質問回答
- Dioxusチームは2024年の残り期間におけるコミュニティの支援に感謝し、アプリ開発を変えるための協力を求めている
1件のコメント
Hacker Newsでの意見
作業を始めた時点ではバージョン 0.2 で、今回の 0.5 の変更により、当時感じていた複雑さはほぼなくなったように見えます。まだ自分では試していませんが、ライフタイム(lifetime)の排除と、何度も clone し続けなければならない負担の軽減で、ずっと快適になりそうです。
デスクトップ、Web/wasm、モバイルなどにデプロイできる ネイティブなリアクティブ UI を目指す Rust フレームワークはかなり多く(https://github.com/flosse/rust-web-framework-comparison)、選び間違えると放置されたフレームワークを保守するか、つらい移行を強いられるのではないかと心配しています。
Rust の HTTP サーバーフレームワークも同じように多かったものの、今は Axum、Actix、Rocket が先行しているように見え、コミュニティの流れは Axum に移っているようなので、Actix を選んだのは間違いだったのかとも思っています。Dioxus が勝ちそうな兆しはあるのか、大きなコミュニティや YC の支援、勢い以外に、今選んでもよいと言える指標があるのか気になります。
Leptos と Yew も主要な競合なのか、Dioxus より優れている点・劣っている点は何なのかも知りたいです。会社としては Rust、Actix、Bevy にかなり投資しており、今後は Bevy と Dioxus のようなフレームワークを組み合わせて、ネイティブのデスクトップ/モバイルクライアントを作ろうとしています。
それから Dioxus という名前が Pokémon の Deoxys に由来しているのかも気になります。ロゴはそんな雰囲気ですが、コードベースには Pokémon への参照がありません。
9か月ほど前に Dioxus で sshfs 用の GUI フロントエンドを作りましたが、開発者がまだ実装を終えていない機能の壁にぶつかるまでは、本当に素晴らしい GUI フレームワーク だという印象でした。
異なるコンテキスト間での状態共有も時々つらいですが、言語や基盤技術に関係なく、これまで使ったすべての GUI フレームワークでそうでした。Dioxus 0.5 はこの点で大きな前進のように見えます。私のブログは HTML のかなりの部分を Dioxus でレンダリングしていますが、限界まで押し込むような用途ではないので、とてもよく動いています。
ただ、ライフタイム排除 の解決策には少し首をかしげています。generational-box は一種の貧者のガベージコレクタではないかと思うのですが、性能への影響がどうだったのか気になります。
ついでに、
[generational-box]([https://crates.io/crates/generational-box](<https://crates.io/crates/generational-box>))のリンクは壊れています。use_hookがコンポーネントのライフタイムの間値を所有するので、コンポーネントが消えるとその値も drop されます。すべての signal は依然としてuse_hookを使っているため、ライフタイムも同じです。use_hookの外でGenerationalBox::new()を呼ぶことは通常推奨されないため、性能への影響はありません。ループ内で
GenerationalBox::new()を乱用すると、そのゴミはコンポーネントが drop されるまで残りますが、ほとんどの場合は Map や Vec に push/pop するでしょうし、その場合は通常のメモリ意味論が適用されます。一種の アリーナアロケーション だということは分かりますが、「コピーなしのコピー」をどうサポートしているのか、また内部的に世代付きの
RefCellアリーナを作り、GenerationalBoxがCopyだという説明がなぜ安全なのか理解できません。静的データへのポインタを作れることは分かりますが、静的ライフタイムを持たない値の場合はどうなるのか気になります。
データ参照はプログラムの存続期間中維持されます。generational box はプログラムの存続期間より短く生きるデータを入れられるようにしますが、そのデータは参照を含んではいけません。入れたデータを drop すると、その box は別の割り当てに再利用されます。
中央集約型のアリーナではなく box を使う点を除けば、世代別アリーナと非常によく似たアプローチで、ロックの問題を避けるための選択です。drop された後に
Copy参照でデータへアクセスしようとすると、分かりやすいエラーメッセージとともに失敗します。Copyには特定の意味があります。ある型が
Copyトレイトを実装しているということは、memcpyでコピーできるという意味で、ディープコピーではなく シャローコピー に近いものです。なので「コピーなしのコピー」ではなく、
Copyでない型をCopy型のように扱えるようにするものだと理解しています。README には静的コンテンツが必要だと書かれているので、静的ライフタイムを持たない値については「そうはできない」が答えです。Dioxus は React が成功した要素を多く取り入れつつ、その上で素早く革新し、配布しているところが良いです。今回のリリースの signals を試すのが楽しみです。
React が JS と Web にもたらした良いことや、その知名度は認めますが、2024 年に言語や DSL をゼロから設計できるなら、「リアクティブ UI」のコードが必ず React のように見えなければならないとは思いません。
SwiftUI が完璧だという意味ではありませんが、SwiftUI で書くコードは、React で似たようなコードを書くときよりも、はるかにすっきり整理され、区切られているように感じます。
クロスプラットフォームGUIでJSXを使う本当の利点は、Web向けの既存ライブラリを再利用できることくらいですが、RSXは開発者がJSXの概念知識をRSXへ移せるという点以外には、「移植可能な価値」が小さく見えます。むしろReact/JSXパラダイムより客観的に優れていると思える新しいパラダイムを学ぶほうが、よい妥協案です
要するに「SwiftUIだがクロスプラットフォーム」なプロジェクトがあるとよいのですが、まだありません。@Tokamak/TokamakUIは知っていますが、まだかなり未完成で活動も減っているようです
現在はLinux/mac/windowsのネイティブデスクトップアプリのみ対応していますが、WASM、Web、モバイル対応の計画があります
Freenetを設定した人たちが最初に目にする分散型Webサイトになる予定です。数年にわたって断続的に作業してきたKotlinのWebフレームワークKwebとも少し似ていて、特に状態処理と、コードからHTMLへマッピングされるDSL方式が似ています。今のところ気に入っています
https://freenet.org/
https://kweb.io/
実はKotlinのファンで、Kotlin DSLと並行性モデルが好きです
TauriはフロントエンドをWebViewに入れ、ネイティブRust関数とはElectronのようにIPC境界を通じて通信する必要があります
DioxusではRustコードがネイティブ側にあるため、ファイルシステムの読み取りやWebSocketのような処理にIPCは不要です。TauriはフロントエンドをWASMにコンパイルすることを強制し、興味深いRustクレートの中にはwasmにコンパイルできないものも多くあります
IPC境界がないと開発がどれほど単純になるかは、言葉では言い表しにくいです。DioxusのツールはRustだけを対象にしているので、ゼロからバンドル済みの
.appまで1分以内で到達できます。新規ビルドは約12秒、新規バンドルは約20秒ですそれでもTauriがもたらす柔軟性、つまりWeb互換UIなら何でもフロントエンドとして使える点は大いに気に入っており、Tauriアプリ内でDioxusを使うこともできます
DioxusがREADMEで直接扱っているのはよかったですが、両方を使った人たちの実体験も気になります
Tauriでおもちゃのデスクトップアプリを作ってみたところ、Webフロントエンドがキー入力ごとに更新して処理しても遅延がないほどIPCが速いかは確認でき、答えは「はい」でした。大きなファイルをキー入力ごとにフロントエンドからRust層へ送り、またフロントエンドへ戻せるかは、少なくとも私の素朴な実装では「いいえ」でした
TauriとDioxusのどちらにもよい事例は多く、あるケースではDioxusのほうがよさそうです。「私たちのプロジェクトではXとYのためにDioxusまたはTauriを選んだ」といった比較経験談を聞きたいです。Dioxusは本当にすばらしく見えるので、使ってみるのが楽しみです
styloはServoのうちFirefoxと共有されている部分です。長期的には人々をWGPUレンダラーへ移行させたいですが、まだかなり初期段階であり、Dioxusを使う多くの企業は、WebViewがアプリの90%程度には十分よい解決策だと現実的に理解しています
人々がときどき
unsafeを安易に使いすぎるのは理解しますが、標準ライブラリもunsafeだらけで、そこで線を引くのは時々、砂上に線を引くように見えます3か所で使っています。iOSの一部FFI修正、signalに関数呼び出し構文を可能にする実装、ポインタをハッシュとして使うIDにSend/Syncを実装する部分です
最後のものは、今見ると
usizeに置き換えれば取り除けるかもしれませんunsafeを少し過度に恐れることがある、という点には同意しますそれでもクレート作者が自分のクレートから
unsafeを取り除くことを目標にするのが、必ずしも悪い判断というわけではありませんすべての
unsafeを取り除こうとするクレート作者は、ユーザーが負うべき信頼の負担を減らそうとしている場合が多いです。これこそがunsafeキーワードの力だと思います。実際にはtrust_meブロックとcheck_yourself関数に分かれているべきだったのかもしれませんunsafeはメモリ安全性に関する議論を、コード内の非常に狭く監査可能な場所に限定し、同時に信頼についての新しく管理可能な議論を生み出しますuse*という命名規則を作った理由は理解していますが、Dioxusで新しいsignalを作る関数がなぜuse_signalなのか気になりますlet mut count = use_signal(|| 0);は新しいsignalを作る呼び出しではないのですか? signalはレンダリングごとに作り直されず、それこそがsignalの核心ですSolidJSでは
const [count, setCount] = createSignal(0)のようにcreateSignalでsignalを作り、このほうがはるかに理解しやすいですAPI名は重要です。状態フックは異なる動作をし、
useMemoのような補完要求も生じ得るためです。DioxusがReactに近く見せようとしていること以外に、use_signalという名前を選んだ理由があるのか気になりますSolidよりはPreactに近いです
最適化には、UIの動的な断片を別の「diff bin」に分離すること、基本的なメモ化、signalが属性を暗黙的にdirty/managedとしてマークすることが含まれます