Rust UIライブラリ「Dioxus」をReact+Typescriptと比較してみた

# はじめに
これは [株式会社 RetailAI X Advent Calendar 2022](https://qiita.com/advent-calendar/2022/retail-ai-x) の 9 日目の記事です。
前回は MLエンジニアである[@atsukish](https://qiita.com/atsukish)さんの[【MLOps】Vertex AIによるモデルモニタリングサービスの構築](https://qiita.com/atsukish/items/63ed4415a67bfaaef220)でした。

本日は「Rust UIライブラリ「Dioxus」をReact+Typescriptと比較してみた」です。

フロントエンドエンジニアの私は最近Rustに興味を持っているのですが、自分の特に興味のあるフロントエンド分野で学習できるライブラリ等がないかと探していたところDioxusを見つけました。
Reactを触ったことがある方なら割と違和感なくRustの学習ができるのではないかと思いましたので共有します。


## Dioxusとは？
[Dioxus](https://dioxuslabs.com/)はRustでGUIアプリケーションを構築できるクレート（Rustの世界ではライブラリ、フレームワークのことをクレートと呼びます）です。
Reactライクな文法で記述することができて、
・WebAssembly
・Desktop
・Mobile（現在iOSのみ？）
・TUI
など、様々な形で出力することができます。
また、デフォルトで
[・Reduxのような状態管理機能（Fermi）](https://github.com/DioxusLabs/dioxus/tree/master/packages/fermi)
[・ルーティング機能（Router）](https://github.com/DioxusLabs/dioxus/tree/master/packages/router)
も組み込まれているため、発展途上ではありますが現状でもそれなりのフロントエンドは構築できるようになっています。
今回の記事ではWebAssemblyを出力する方法を紹介したいと思います。


## Rust、Dioxusの導入

### １・````rustup````のインストール
まず````rustup````をインストールしましょう。これによりコンパイラである````rustc````,パッケージマネージャの````cargo````等のRust開発に必要なツールを一律でインストールすることが可能です。
下記の公式のやり方でインストールが可能です。

https://www.rust-lang.org/ja/tools/install

また、macOSの場合は````homebrew````でのインストールも可能です。

````terminal
//rustupをインストール
$ brew install rustup-init
$ rustup-init
//シェルの再起動
$ exec $SHELL -l
````

インストールが終わったら念の為バージョン確認をして、インストールされていることを確認しましょう。

````terminal
//rustcのバージョン確認
$ rustc --version

>rustc 1.65.0 (897e37553 2022-11-02)
//cargoのバージョン確認
$ cargo --version

>cargo 1.65.0 (4bc8f24d3 2022-10-20)
````

### ２・周辺ツールのインストール(Trunk,Cargo-edit等)

#### ・````Trunk````

TrunkはRust用のWebAssemblyバンドラーです。（JSでいうWebpack,Viteのようなもの）
````index.html````をエントリーポイントとして、WASM（WebAssembly。以降WASMと呼びます）や画像、CSSをまとめてくれるだけでなく、ホットリロード機能付きの開発サーバーを立ち上げてくれます。
個人的に現状ではWebの開発をするにはTrunkが最適なのではないかと考えます。
Trunkはcargoを利用してインストールすることができます。

````terminal
//Trunkをインストール
$ cargo install trunk
````
また、RustコードをWebAssemblyをコンパイルするために、Rustにコンパイルターゲットを追加する必要があります。
以下のように追加します。
````terminal
$ rustup target add wasm32-unknown-unknown

//※Rustがサポートしているコンパイルターゲットは以下のコマンドで確認できる
$ rustup target list

>aarch64-apple-darwin
aarch64-apple-ios
aarch64-apple-ios-sim
aarch64-fuchsia
aarch64-linux-android
省略...

````

[コンパイルターゲットに関する参考資料](https://doc.rust-lang.org/nightly/rustc/platform-support.html)

#### ・````cargo-edit````

Rustでクレートをインストールする際は````cargo.toml````に指定したクレートを記述する必要がありますが、都度tomlファイルに入力するのは面倒だったりします。
cargo-editは````cargo add {クレート名}````とコマンド名を入力することでtomlファイルにクレートの情報を入力してくれるため便利です。（JSの````npm install````,````yarn add````みたいなもの）
また、クレートによっては使いたい機能を````features````として指定する必要があったりするのですが、cargo-editでは````cargo add {クレート名} --features {機能名}````で入力することが可能です。

### ３・VSCode拡張機能の導入

#### ````rust-analyzer````
[rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)はRustコードの型や未使用関数等のチェック、型推論等をおこなってくれるVSCode拡張機能です。
通常ですと````cargo run````コマンドを実行しなければわからないエラー等がVSCode上で視覚的にわかるようになり、これがあるとないとでは開発効率が全然違うと思いますので、ぜひインストールすることをお勧めします。

#### ````BetterToml````
[Better-Toml](https://marketplace.visualstudio.com/items?itemName=bungcip.better-toml)はtomlファイルのシンタックスハイライト等を行なってくれる拡張機能です。
tomlファイルを編集したい時に便利なのでインストールすることをお勧めします。

#### ````crates````
[crates](https://marketplace.visualstudio.com/items?itemName=serayuzgur.crates)はtomlファイルにあるクレートの依存関係を管理するための拡張機能です。
クレートのバージョンが最新かどうかは今までは[crates.io](https://crates.io/)を確認しなければいけなかったのですが、この拡張機能はtomlファイル上でクレートのバージョンを確認したり、セレクトボックスでバージョンを変更したりできます。
これも非常に便利だと思うのでインストールすることをお勧めします。

#### ````TailwindCSS IntelliSense````
[TailwindCSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)は、TailwindCSSのクラス名の補完をしてくれる拡張機能です。すでに用意されてあるクラスの他、````tailfind.config.js````で定義したクラスの補完もしてくれます。
TailwindCSSを使用する場合、この拡張機能をインストールすることをお勧めします。



### ４・プロジェクトの作成、Dioxusの導入

それでは新しくDioxusのプロジェクトを作成してみましょう。````cargo new {プロジェクト名}````で作成することができます。
その後プロジェクトのディレクトリに移動し、Dioxusを````cargo-edit````を用いてインストールしましょう。

````terminal
//cargoを使用して新しいRustのプロジェクトを作成
$cargo new {任意のプロジェクト名}

$cd {任意のプロジェクト名}

//Dioxusをfeaturesを「Web」に指定してインストール
$ cargo add dioxus --features web
````

インストールしたら、プロジェクトのルートにTrunkのエントリーポイントになる````index.html````を作成します。

````html:index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  </head>
  <body>
    <div id="main"></div>
  </body>
</html>

````
次に````main.rs````ファイルにDioxusのコードを記述します。

````rust:main.rs
use dioxus::prelude::*;

fn main() {
    dioxus::web::launch(app);
}

fn app(cx: Scope) -> Element {
    cx.render(rsx! (
        div { "Hello, world!" }
    ))
}
````

あとは````trunk serve````コマンドを入力してローカルサーバーを起動させ、ブラウザを開き````localhost:8080````が開ければOKです。


### ５・TailwindCSSの導入（お好みで）
Dioxusには[TailwindCSS](https://tailwindcss.com/)を導入することも可能です。
[こちらの記事](https://qiita.com/RyoWakabayashi/items/cea19d7e70d0c46eaa4b#tailwind-css)が参考になるかと思います。
↑の記事に補足すると、このままではTailwindCSSのインテリセンスが効かないので、Rustコード上でTailwindCSSのインテリセンスが効くように設定をする必要があります。以下の設定を````settings.json````に追加します。

````js:settings.json
　　//「class: ""」という構文でもTailwindCSSのインテリセンスが効くようにする
 "tailwindCSS.experimental.classRegex": ["class:\\s*\"([^\"]*)\""],
 //.rsファイルもhtmlとみなし、インテリセンスの対象とする
 "tailwindCSS.includeLanguages": {
    "rust": "html",
    "*.rs": "html"
  }
````
こうすることで、````.rs````ファイル上でもTailwindCSSのインテリセンスが効くようになります。
![スクリーンショット 2022-12-06 9.55.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/574551/48985bb2-b0d8-1072-9452-3507d198e4bd.png)

## Reactとの比較

### １・関数コンポーネントの作成
#### React

````react
export const Sample = () => {
    return (
        <div>hello world</div>
    )
}
````

#### Dioxus
Dioxusは````rsx!````マクロを使用してコンポーネントを作成します。

````rust
use dioxus::prelude::*;

#[allow(non_snake_case)]
pub fn Sample(cx: Scope) -> Element {
    cx.render(rsx! {
            div {
                "hello world"
            }
        }
    )
}
````

````#[allow(non_snake_case)]````としているのは、パスカルケースで関数を定義するとrust-analyzerで警告が発生してしまうので記述しています。



### ２・Propsの定義、渡し方

#### React

````react
type Props {
    text: string
}

export const sample = ({text}: Props) => {
    return (
        <div>{text}</div>
    )
}
````

#### Dioxus
DioxusでPropsを定義する方法として
・構造体（Struct）を使用して定義する方法
・````inline_props````マクロを使用して定義する方法
の二種類があります。

````rust
//構造体を使用して定義する方法
#[derive(Props, PartialEq)]
pub struct Props {
    text: String,
}

#[allow(non_snake_case)]
pub fn Sample(cx: Scope<Props>) -> Element {
    cx.render(rsx! {
        div { "{cx.props.text}" }
    })
}
````

````rust
//inline_propsマクロを使用して定義する方法
#[allow(non_snake_case)]
#[inline_props]
pub fn Sample(cx: Scope, text: String) -> Element {
    cx.render(rsx! {
        div { "{text}" }
    })
}
````

構造体を用いる方法はReact+Typescriptと同じような感じで馴染みがあるのではないかと思います。
````inline_props````マクロを用いる方法は構造体を用いる方法よりもスッキリと記述できるのが特徴です。
基本inline_propsを用いる方法で問題ないかとは思いますが、[公式ドキュメント](https://dioxuslabs.com/guide/components/propsmacro.html#the-inline_props-macro)によると、「Propドキュメントを制御できない可能性があるので、ライブラリの作者はこれを使用しないでください」とのことでした。

### ３・State管理、Hooks

#### useState

#### react
````react
export const sample = () => {
    const [value, setValue] = useState("")
    
    return (
        <>
            <div>{value}</div>
            <button onClick={() => setValue("sample")}>button</button>      
        </>
    )
}
````
#### Dioxus
````rust
pub fn sample(cx: Scope) -> Element {
    let value = use_state(&cx, || "")

    cx.render(rsx!(
        button { 
            onclick: move |_| value.set("sample"), 
            "button"
        }
    ))
}
````
DioxusはHooksを定義する際にはSetterは書かないようです。


### ４・演算子
React(TS,JS)では````&&````や````三項演算子````等の演算子を用いて実装することがあると思いますが、Reactの演算子を用いた実装法をDioxusではどうやって実装するのか紹介したいと思います。(以下の例からは外側の関数宣言のコード等を省略した状態で書きますので、ご了承ください。)

#### ````&&````
Reactでは結構出てくると思いますが、````左辺の条件を満たす時に右辺の処理を実行する````演算子です。

````react
//isOpenがtrueの時にdivタグを表示する

const isOpen = true

return (
    isOpen && { <div>open!</div> }
)
````

Dioxusだとこんな感じになります。
````rust
let is_open = true;

is_open.then(|| rsx!(
    div {
        "open!"
    }
))
````


#### ````三項演算子````
#### react
````react
const hello = "world"

return (
    {hello === "world" ? <div>variable is world</div> : <div>other</div>}
)
````
#### Dioxus
Rustでは三項演算子がありません。そのため````if式````（Rustの````if````は文ではなく式です）で同等の機能が実装できます。
````rust
let hello = "world";

if hello == "world" {
    rsx!(
      div {
          "variable is world"
      }
    )
} else {
    rsx!(
        div {
            "other"
        }
    )
}
````

また条件分岐が多岐に渡る場合は、TS/JSの標準機能にはない（[ts-pattern](https://github.com/gvergnaud/ts-pattern)のようなライブラリで代替はできますが）Rustの機能として````match````式が使えると思います。
可読性も良く、個人的におすすめです。
````rust
let hello = "world";

match hello {
    "world" => rsx!(
        div {
            "hello world"
        }
    ),
    "guys" => rsx!(
        div {
            "hello guys"
        }
    ),
    //「　_　」はそれ以外の条件の場合
    _ => rsx!(
        div {
            "other"
        }
    )
}
````

## Reactに慣れていると戸惑うかもしれないRustの機能

### １・所有権
まず第一に所有権が挙げられると思います。
RustはGCがないため、基本的に````let````で宣言した変数はスコープから外れると破棄されます。
Reactと違って変数の定義の仕方はもちろん、変数を使用する際もそのまま使うのか、不変参照(&をつける。↑でもuseStateのセクションで行なっています)にするか、可変参照(&mutをつける)にするか等に気を配る必要が出てきます。


### ２・モジュール宣言、インポートの方法

Reactの場合はモジュールのインポートは以下のようにして行います。

````react
//default export＋絶対パスの場合
import Sample from 'src/components/Sample'
//named exportの＋相対パスの場合
import { Sample } from './components/Sample'
````

Rustの場合モジュールをインポートする際は、基本的に
1. モジュールと同階層に````{モジュール名}.rs````ファイルを置いて、その````{モジュール名}.rs````ファイル内で作成してあるモジュール、ファイルを````pub mod　{ファイル名}````で公開する
1. ````main.rs````または````lib.rs````に同階層にあるモジュールを````mod {モジュール名}````で使用する
1. ````use crate::{モジュール名}````,````use super::{モジュール名}````等でインポートする

という手順を踏む必要があります。
言葉にすると訳がわからないと思うので以下の図のようなディレクトリ構成の場合のやり方を説明します。
````
src
 ┣components
 ┃     ┣atoms
 ┃     ┃   ┣AtomsSampleA.rs
 ┃     ┃   ┗AromsSampleB.rs
 ┃     ┣molecules
 ┃     ┃   ┗MoleculesSample.rs
 ┃     ┣organisms
 ┃     ┃   ┗OrganismsSample.rs
 ┃     ┃
 ┃     ┣aroms.rs　←モジュールと同階層に{モジュール名}.rsファイルを置く
 ┃　　　┣molecules.rs　←モジュールと同階層に{モジュール名}.rsファイルを置く
 ┃　　　┗organisms.rs　←モジュールと同階層に{モジュール名}.rsファイルを置く
 ┃
 ┣components.rs　←モジュールと同階層に{モジュール名}.rsファイルを置く
 ┗main.rs
````
src配下の階層には「components」モジュールがあるため、同階層に「components.rs」を作成します。
components配下の階層には「atoms」「molecules」「organisms」があるため、同階層に「atoms.rs」「molecules.rs」「organisms.rs」を作成します。
次に````{モジュール名}.rs````で、各モジュール内にあるモジュール、ファイルを公開します。
````components.rs````の場合はこうなります。

````rust
pub mod aroms;
pub mod molecules;
pub mod organisms;
````

````aroms.rs````の場合はこうなります。

````rust
//↓パスカルケースで記述しているのでこれを記述していますが、スネークケースで記述している場合は必要ありません
#![allow(non_snake_case)]
pub mod AtomsSampleA;
pub mod AtomsSampleB;
````

同様に````molecules.rs````,````organisms.rs````も自モジュール内のファイルを公開していきます。
最後に````main.rs````内で同階層のモジュールを読み込みます。

````rust:main.rs
mod components;
````

これで````use````を用いてモジュールをインポートすることができるようになります。
````rust:AtomsSampleA.rs
//絶対パス
use crate::components::molecules::MoleculesSample::MoleculesSample;
//相対パス
use super::super::molecules::MoleculesSample::MoleculesSample;
````

TS/JSとは全然勝手が違って戸惑うかとは思いますが、Rustではこのようにモジュールをインポートします。
ちなみにネットで調べると同階層に````{モジュール名}.rs````ではなく````mod.rs````を用いてモジュールを公開する方法がかなり出てきますが、mod.rsを用いる方法は2015エディションの方法らしく、2021エディションの現在は推奨されていません。（後方互換性を保つためか一応今でもできるようにはなっていますが、同名の````mod.rs````が大量にコード上に出てくるようになるのでコードの規模が大きくなるほど見づらくなります。）[参考資料：Rust Edition Guide](https://doc.rust-lang.org/edition-guide/rust-2018/path-changes.html#no-more-modrs)




### ３・構造体（Struct）、Trait、継承
構造体やトレイトの概念もReactには登場しないので最初は少し戸惑うかもしれません。
構造体とは````データ型の要素を集めたもの````でTypescriptでいう````interface````や````type````に近いものです。
本記事の````Propsの定義、渡し方````のセクションにも構造体を使用してPropsを定義していた箇所があったかと思います。
````rust
#[derive(Props, PartialEq)] //←これは何？
pub struct Props {
    text: String,
}
````
もう一度見た時に、````#[derive()]````の記述があることに気がつきますが、これにはRustの````Trait````と````継承（derive）````の機能が関わってきます。
[Trait(トレイト)](https://doc.rust-jp.rs/book-ja/ch10-02-traits.html#%E3%83%88%E3%83%AC%E3%82%A4%E3%83%88-%E5%85%B1%E9%80%9A%E3%81%AE%E6%8C%AF%E3%82%8B%E8%88%9E%E3%81%84%E3%82%92%E5%AE%9A%E7%BE%A9%E3%81%99%E3%82%8B)とは、型に定義できる共通の振る舞いのことです。
Traitは自分で実装することもできるのですが、````#[derive()]````アトリビュートを使用して標準で用意されているTraitを構造体に実装することが可能です。[deriveできる標準Trait](https://doc.rust-jp.rs/rust-by-example-ja/trait/derive.html)
これにより、構造体に様々な機能を付与することが可能になります。

この他にも、構造体には````impl````キーワードでメソッドを関連づけることもできます。


### ４・raw identifier
Dioxusで````input````要素の````type````を指定しようとすると、以下のようなエラーに遭遇します。

![スクリーンショット 2022-12-08 12.04.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/574551/970f72ef-f5dc-c6c8-368d-84f11ed60ba9.png)

これは````type````というキーワードが既に型定義をする際のキーワードとして存在するためにtypeというキーワードを入力すると型定義をする際のキーワードとして認識されてしまいます。
「じゃあinput要素の````type````が指定できないじゃん」と思われるかもしれませんが、[raw identifier](https://doc.rust-lang.org/rust-by-example/compatibility/raw_identifiers.html)という仕組みを利用すれば可能になります。
````r#{予約語}````という形で記述すれば、そのキーワードは予約されたキーワードではないということになり、使用することができます。
従ってinput要素の````type````を指定したいときはこのように記述すればOKです。
![スクリーンショット 2022-12-08 13.28.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/574551/03c43d6c-bca1-e4ed-84c4-fa9a4c7a8f9e.png)




## まとめ
本記事ではDioxusとRustの基本的な使い方について紹介しました。Dioxusはまだまだ発展途上ですが、様々なプラットフォーム向けにコンパイルできる点が魅力的だと感じました。Web、Desktop、Mobile、全てをRustで実装・・なんてできたらいいですね。
私自身Rustの理解がまだまだなので、今後も継続的に学習してこの記事のコンテンツをもっと充実させようと考えています！
次回は[@nishi_takuya](https://qiita.com/nishi_takuya)さんの[テストケースを作るときに気を付けていること](https://qiita.com/nishi_takuya/items/8ce8def1644b6870c534)です！

## 参考資料
[Rust Document](https://doc.rust-jp.rs/book-ja/title-page.html)
[Dioxus Document](https://dioxuslabs.com/guide/index.html)
[The Rust Edition Guide](https://doc.rust-lang.org/edition-guide/introduction.html)
[The rustc book](https://doc.rust-lang.org/nightly/rustc/what-is-rustc.html)
[Rust入門](https://zenn.dev/mebiusbox/books/22d4c1ed9b0003)