【Rust】icedでデスクトップアプリケーションを作る

# はじめに

こんにちは、梅雨です。

今回は iced クレートを用いてデスクトップアプリケーションを作る方法を解説していきたいと思います。

iced はまだメジャーバージョンがリリースされていないため、マイナーアップデートによって大きく変更が加わります。この記事では最新のバージョンを用いて、簡単なカウントアプリを作成していきます。

# iced とは？

iced とは、Rust のクロスプラットフォーム GUI ライブラリであり、デスクトップや Web 向けのアプリケーションを簡単に作成できます。

https://iced.rs

# 環境構築

プロジェクトはCargoを用いて作成します。

```bash
$ cargo new iced-demo
$ cd iced-demo
```
iced をインストール。

```bash
$ cargo add iced
```

`Cargo.toml`を確認すると、icedの`0.13.1`が入っています。（2024/12/11現在の最新バージョン）

```toml:Cargo.toml
[package]
name = "iced-demo"
version = "0.1.0"
edition = "2021"

[dependencies]
iced = "0.13.1"
```

プロジェクトのビルド & 実行を行い、`Hello, world!` が出力されることを確認しましょう。

```bash
$ cargo run
# --- 中略 ---
Hello, world!
```

# アプリケーションの作成

iced は Elm という GUI 開発向けプログラミング言語にインスピレーションを受けており、The Elm Architecture に基づいた構成となっています。

https://guide.elm-lang.org/architecture

Elm は以下の3つの要素をコアとしています。

- Model: アプリケーションの状態
- View: 各状態に対応する見た目
- Update: メッセージに基づいて状態を更新するロジック

簡単にいうとステートによって見た目の制御を行う構成であり、React 等のモダンなフロントエンドライブラリを用いたことがある人にはかなりわかりやすいと思います。

まずはステート（＝Model）を用意しましょう。今回必要なステートはvalueで、型はi32としました。Rustには数値の型がたくさんあり、符号の有無やビット数を細かく指定できます。

|大きさ|符号付き|符号なし|
|---|---|---|
|8-bit|i8|u8|
|16-bit|i16|u16|
|32-bit|i32|u32|
|64-bit|i64|u64|

```rust:main.rs
struct Counter {
  value: i32,
}
```

続いてメッセージを用意します。メッセージとはステートに変化を加えるアクションのようなもので、今回は足し引きを行えるように `Increment` と `Decrement` の二つのメッセージを定義しました。

```rust:main.rs
enum Message {
  Increment,
  Decrement,
}
```

ステートとメッセージが用意できたら、これらを紐づけるロジック（＝Update）を記述していきましょう。引数にはステートへの参照とメッセージをとります。

```rust:main.rs
fn update(counter: &mut Counter, message: Message) {
  match message {
    Message::Increment => {
      counter.value += 1;
    }
    Message::Decrement => {
      counter.value -= 1;
    }
  }
}
```

最後に見た目（＝View）を記述していきます。iced にはさまざまなウィジェットが用意されており、例えば`column`を使うと要素を縦並びにすることができます。

見た目はステートの変更によって自動で更新されるので、見た目自体を更新するようなコードは必要ありません。

```rust:main.rs
use iced::widget::{button, column, text};
use iced::Element;

fn view(counter: &Counter) -> Element<Message> {
  return column![
    button("+").on_press(Message::Increment),
    text(counter.value),
    button("-").on_press(Message::Decrement)
  ]
  .into();
}
```

これでアプリケーションの準備が終わりました。main 関数の中にウィンドウを起動するコードを書きます。

```rust:main.rs
fn main() {
  iced::run("Count App", update, view).unwrap();
}
```

このとき、`Counter` と `Message` に対し、以下のトレートを実装する必要があります。

```diff_rust:main.rs
+ #[derive(Default)]
struct Counter {
  value: u32,
}

+ #[derive(Clone, Debug)]
enum Message {
  Increment,
  Decrement,
}
```

以上で実装は完了です。今までのコードはこのようになっています。

```rust:main.rs
use iced::widget::{button, column, text};
use iced::Element;

#[derive(Default)]
struct Counter {
  value: u32,
}

#[derive(Clone, Debug)]
enum Message {
  Increment,
  Decrement,
}

fn update(counter: &mut Counter, message: Message) {
  match message {
    Message::Increment => {
      counter.value += 1;
    }
    Message::Decrement => {
      counter.value -= 1;
    }
  }
}

fn view(counter: &Counter) -> Element<Message> {
  return column![
    button("+").on_press(Message::Increment),
    text(counter.value),
    button("-").on_press(Message::Decrement)
  ]
  .into();
}

fn main() {
  iced::run("Count App", update, view).unwrap();
}
```

アプリケーションを実行します。

```bash
$ cargo run
```

ウィンドウが立ち上がり、カウントアプリが表示されていれば成功です。

# おわりに

今回は　iced を用いて簡単なカウントアプリを作成しました。

発展的なアプリケーションも基本的には今回と同じような流れて作成することができるので、興味のある方はぜひ挑戦してみてください。