Rust初心者がRust + actix-web + diesel(MySQL) + serdeでREST APIを作ってみた


## Rustとは
https://www.rust-lang.org/ja

RustはFirefoxで有名なMozilla社によって開発されていたオープンソースのプログラミング言語です。2021年からは[Rust Foundation](https://foundation.rust-lang.org/)が立ち上がり開発を主導しているようです。



Rustの大きな特徴としては以下の点があります。
-  パフォーマンス
メモリ効率が高くランタイムやガベージコレクタがないためC/C++と同等の処理速度を誇ります。
パフォーマンス比較の記事はたくさんありますが以下の記事が複数言語を比較していて分かりやすかったです。

https://qiita.com/reki2000/items/55ef54b96b26d80ad694#%E7%B5%90%E8%AB%96


- 安全性
コンパイラ言語であるRustは豊かな型システムと所有権モデルによりメモリ安全性とスレッド安全性が保証されているのでコンパイル時に様々なバグを排除することが可能です。
Rustには所有権とライフタイムという概念があり以下のようなコードはコンパイル時にエラーになります。

```rust
let mut data = vec![1, 2, 3];
// 内部データの参照を取る
let x = &data[0];

// しまった! `push` によって `data` の格納先が再割り当てされてしまった。
// ダングリングポインタだ! メモリ解放後の参照だ! うわーー!
// （このコードは Rust ではコンパイルエラーになります）
data.push(4);

println!("{}", x);
```
[ref: 所有権とライフタイム](https://doc.rust-jp.rs/rust-nomicon-ja/ownership.html)

- コミュニティ
80,000人のdeveloperを対象にした2021年のstackoverflowのアンケート調査によると、Rustが6年連続でプログロマーに愛される言語ランキングで1位を獲得しているようです。
![スクリーンショット 2022-04-12 8.41.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/650287/d2f0c003-ef6b-4a31-8eb9-c0dabb3cef63.png)
[ref: Stack Overflow Developer Survey 2021](https://insights.stackoverflow.com/survey/2021#most-loved-dreaded-and-wanted-language-love-dread)

とりあえずRustがなんとなく凄そう？なのは分かったので、実際に環境構築して簡単なREST APIを作成することをゴールに進んでいきましょう。



## Rust環境構築

動作環境は以下の通りです。
- macOS Monterey (v12.1)
- MacBook Pro (14インチ、2021)
- チップ: Apple M1 Max

### rustupをinstall
Rustはrustupというツールによってインストール・管理されています。以下のコマンドでinstallしましょう。

```bash
$ brew install rustup-init
$ rustup-init
 
$ exec $SHELL -l # restart shell
```
もしshellをfishにしている方はこちらのコマンドよりパスを通してください。

```bash
set -U fish_user_paths $fish_user_paths $HOME/.cargo/bin
```

これでrustupコマンドとビルドシステム兼パッケージマネージャのcargoもinstallされたかと思います。

```bash
$ rustup --version
> rustup 1.24.3 (2021-05-31)

$ cargo --version
> cargo 1.59.0 (49d8809dc 2022-02-10)
```

### プロジェクトの作成
rustupをinstallできたら早速プロジェクトを作成してみましょう。

```bash
cargo new rust_practice

```
作成されたプロジェクト、 `rust_practice`を開くとCargoが2つのファイルと1つのディレクトリを生成してくれたことがわかるかと思います。
Rustでは基本的に`src/`以下にソースコードを記入していきます。 自動で作成された`src/main.rs`を見ると

```rust:src/main.rs
fn main() {
    println!("Hello, world!");
}
```
と書かれていることが分かるかと思います。
早速cargo runコマンドでコンパイルしてこのスクリプトを実行してみます。

```bash
$ cargo run
> Compiling rust_demo v0.1.0 (/Users/andrew/repos/rust_demo)
    Finished dev [unoptimized + debuginfo] target(s) in 0.28s
     Running `target/debug/rust_demo`
Hello, world!
```
簡単にHello, world!まで実行できてしまいましたね。

それでは今回はREST APIを作るということでweb frameworkを導入していこうかと思います。


## actix-webを導入する
Rustのweb frameworkは複数あり、有名どころだと[rocket](https://rocket.rs/)や[actix-web](https://actix.rs/)があります。以下のリンク先で様々なRustのweb frameworkが比較されているので気になる方は見てみてください。

https://github.com/flosse/rust-web-framework-comparison#server-frameworks


今回はRustの数あるweb frameworkの中でも軽量でかつパフォーマンスがいいactix-webを選択することにしました！
以下のリンク先に複数言語のweb frameworkのパフォーマンス比較が載っています、その中でもactixは上位にランクインしてますね。

https://www.techempower.com/benchmarks/#section=data-r20&hw=ph&test=json

### cargo-editを導入する
作成されたプロジェクトを見ると以下のようなファイルが作成されているかと思います。

```toml:Cargo.toml
[package]
name = "rust_demo"
version = "0.1.0"
edition = "2021"

[dependencies]
```

このファイルは外部ライブラリを管理するためのもので、dependencies以下に依存関係を記述していきます。ちなみにRustではライブラリのことを[クレート（crate）](https://doc.rust-jp.rs/book-ja/ch07-01-packages-and-crates.html)と呼びます。

このファイルに直接

```
[dependencies]
actix-web = "version"
```

のように記述してもいいのですがcargo-editというツールをinstallするとcliでCargo.tomlを編集することができるので入れてみましょう。

```bash
$ cargo install cargo-edit
```

### actix-webをinstall
そして早速actix-webをinstallします。

```bash
cargo add　actix-web
```

Cargo.tomlが以下のように更新されたかと思います。

```toml:Cargo.toml
[package]
name = "rust_demo"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html


[dependencies]
actix-web = "4.0.1"
```

これでactix-webのinstallは完了です。
それではとりあえずgetのAPIを一つ作ってみます。
[公式ページ](https://actix.rs/)にある通り、main.rsを以下のコードに置き換えてみましょう

```rust:main.rs
use actix_web::{get, web, App, HttpServer, Responder};

#[get("/hello/{name}")]
async fn greet(name: web::Path<String>) -> impl Responder {
    format!("Hello {name}!")
}

#[actix_web::main] // or #[tokio::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .route("/hello", web::get().to(|| async { "Hello World!" }))
            .service(greet)
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}
```

そしてcargo runを実行し、`http://localhost:8080/hello`にアクセスするとweb serverが立ち上がっていることが分かります。

少し公式ページから持ってきたコードを見てみます。
まず、Rustではcrateのimportを`use`を使って行います。そして`::`は階層を表し、つまり`use actix_web::web` は`actix_web/web.rs`をimportしているということです。そして同一階層の複数のmoduleをimportしたい場合は`{}`で括って一括で宣言できます。actix_webのgithub repositoryを見るとよりしっくりくるかと思います。

https://github.com/actix/actix-web/tree/master/actix-web

Rustの関数は`fn`で定義し、関数の引数と返り値に型定義をしているのが特徴的です。この型定義を間違えるとコンパイル時にエラーとなります。

それではweb APIを立ち上げることに成功したので次はDBと繋いでいきます。

## dieselを導入する
RustのORMはdieselが現時点では一強です。

https://diesel.rs/

### install
actix_web同様`cargo add diesel`でcrateをimportしていきます。今回はDBにMySQLを使うので--feature flagを使ってinstallしていきます。featureフラグとはコンパイル時にクレートの特定の機能の有効にするためのフラグです。以下コマンドでinstallしていきましょう。(r2d2は後述)
```bash
 cargo add diesel --features mysql --features r2d2  
```
またDBの情報を.envファイルから読み込ませるようにしたいので[dotenv](https://crates.io/crates/dotenv)もinstallします。

```bash
cargo add dotenv
```

https://crates.io/crates/dotenv

### diesel-cliでmigrationの実行
そうしたらまずはDBにtableを作成していきたいと思います。
migrationを実行するためのcli、`diesel-cli`をinstallしておきましょう。

```bash
# diesel cliをinstall
cargo install diesel_cli --no-default-features --features mysql
```

installが完了したらdieselコマンドが使えるようになっているはずです。以下コマンドでmigration設定ファイルを作成していきます。

```bash
# migrations dirを作成
diesel setup

# migration file作成
diesel migration generate create_users
> Creating migrations/2022-04-12-132620_create_users/up.sql
> Creating migrations/2022-04-12-132620_create_users/down.sql
```

作成されたsqlファイルに以下のようにupとdownのクエリを書きます。

```sql:migrations/2022-04-12-132620_create_users/up.sql
CREATE TABLE users
(
    id INT PRIMARY KEY AUTO_INCREMENT,
    email VARCHAR(64) NOT NULL
);
```

```sql:migrations/2022-04-12-132620_create_users/down.sql
DROP TABLE users;
```

Migrationを実行していきます。DATABASE_URLを環境変数もしくは.envに設定すれば接続先のDBを指定できるので`.env`ファイルを作ってしまいましょう。

```:.env
#{user}, {password}, {port}にはlocalで立ち上がっているMySQLへのログイン情報を入れてください。
DATABASE_URL=mysql://{user}:{password}@127.0.0.1:{port}/rust_practice
```

そしたらMySQLに入り先に今回使用するDB、`rust_practice`を作成しておきましょう。

```
create database rust_practice;
```

準備は整ったので以下コマンドでmigrationを実行します。

```
diesel migration run
```

migrationが完了すると`__diesel_schema_migrations`と`users`tableが作成されていることが分かります。

```sql
mysql> show tables;
+----------------------------+
| Tables_in_rust_practice    |
+----------------------------+
| __diesel_schema_migrations |
| users                      |
+----------------------------+
2 rows in set (0.01 sec)
```

`__diesel_schema_migrations`にはmigrationのバージョンが入るようになっております。
さらに上記コマンドで自動で`src/schema.rs`が作成されているかと思います。
```rust:src/schema.rs
table! {
    users (id) {
        id -> Integer,
        email -> Varchar,
    }
}
```

定義したSQLを元にRustでの型を定義したschemaファイルを作成してくれます。

次はmodelを定義していきます。`model.rs`を作成し以下のようにUserモデルを定義します。

```rust:src/model.rs
pub struct User {
    pub id: i32,
    pub email: String,
}
```

`struct`は構造体と呼ばれ、データ型の要素を集めたものです。これでmodelの作成も完了です。

### db connection poolの作成
次に`src/db.rs`ファイルを作成し、そこにdieselを用いてDBと接続するためのコードを記述していきます。
connection poolを作成しactix_webにinjectできるようconnection pool管理用crateのr2d2もinstallしていきます。

```bash
cargo add r2d2
```

https://github.com/sfackler/r2d2

以下のように`db.rs`を編集していきます。

```rust:src/db.rs
use diesel::mysql::MysqlConnection;
use diesel::r2d2::ConnectionManager;
use dotenv::dotenv;

pub type Pool = r2d2::Pool<ConnectionManager<MysqlConnection>>;

pub fn establish_connection() -> Pool {
    dotenv().ok();

    std::env::set_var("RUST_LOG", "actix_web=debug");
    let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");

    // create db connection pool
    let manager = ConnectionManager::<MysqlConnection>::new(database_url);
    let pool: Pool = r2d2::Pool::builder()
        .build(manager)
        .expect("Failed to create pool.");
    pool
}

```

`use`を使って外部crateを読み込むところは同じですが、`pub`をつけて`main.rs`で参照したい関数やtypeを定義しております。
変数前につく`let`はRustは変数定義のために使われるものです。Rustではデフォルトで変数はイミュータブル(変更不可能)になっており、変更可能にするには明示的に`let mut foo = xxx`とする必要があります。

### mainでimport
そうしたらmain.rsで先ほど作成した`db.rs` moduleをimportしていきます。

```rust:src/main.rs
mod db;
```

と書くことでcrate内のファイル(module)を読み込むことができます。(`use`では動かないのでご注意ください。)
実はRustではdefaultでは`src/main.rs`または`src/lib.rs`で`mod`を用いてimportされているmoduleを除いてコンパイル対象になりません。ですのでschemaとmodelも`mod`でimportしておきます。
またRustは2018にメジャーアップデートを行いRust2018と呼ばれる記法とRust2015で記法が異なる場合があります。
dieselは一部Rust2018の記法に対応していないので以前の`use`の書き方である以下記法でcrateをimportする必要があります。

```rust:src/main.rs
#[macro_use]
extern crate diesel;

mod db;
mod model;
mod schema;
```



### connection poolをactix_webにinject
connection poolをinjectするために以下のようにコードを書き換えます。

```rust:src/main.rs
#[macro_use]
extern crate diesel;

use actix_web::web::Data;
use actix_web::{get, web, App, HttpServer, Responder};
mod db;
mod model;
mod schema;

#[get("/users/{id}")]
async fn get(db: web::Data<db::Pool>, id: web::Path<String>) -> impl Responder {
    // db connectionが利用可能に！
    let conn = db.get().unwrap();
    format!("Hello {id}!")
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    // db moduleからestablish_connection関数をimport
    let pool = db::establish_connection();

    // app_dataを用いactix_webにdb poolをinject
    HttpServer::new(|| App::new().app_data(Data::new(pool.clone())).service(get))
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
}
```

これでdb poolを各エンドポイントで使用可能になりました！
次はget関数に注目して変更を加えていきます。

## Serde（serializer）の導入

まず対象のidのユーザーをjsonで返却するAPIを作成したいのでdieselで定義したmodelをjsonにserializeする必要があります。そのためserdeというserializerのcrateを追加します。

https://serde.rs/

```
cargo add serde --features derive 
```

`model.rs`を以下のように修正します。

```rust:src/model.rs
use crate::schema::users;
use serde::{Deserialize, Serialize};

#[derive(Queryable, Insertable, Deserialize, Serialize)]
#[table_name = "users"]
pub struct User {
    // idはauto_incrementなので不要
    pub email: String,
}

```

そして`main.rs`の`get`関数を以下のようにします。

```rust:src/main.rs
// 追加
use diesel::ExpressionMethods;
use diesel::QueryDsl;
use diesel::RunQueryDsl;

#[get("/users/{id}")]
async fn get(db: web::Data<db::Pool>, path: web::Path<i32>) -> Result<impl Responder> {
    let conn = db.get().unwrap();
    let id = path.into_inner();
    let user = schema::users::table
        .select(schema::users::email)
        .filter(schema::users::id.eq(id))
        .load::<String>(&conn)
        .expect("error");

    Ok(web::Json(user))
}
```

これでtableに試しに適当なusersデータを作成し、作成されたidをパスに含め`http://localhost:8080/users/1`へアクセスしてみます。すると作成した`users`データがjsonで返却されてるかと思います！

これで一つMySQLからデータを取得し返却するAPIをRustで作成することができましたね。

あとは同じ要領でPOST, PUT, DELETEのエンドポイントを作ってみます。
コードとしては以下のようになります。

```rust:src/main.rs
// POST API
#[post("/users")]
async fn post(db: web::Data<db::Pool>, item: web::Json<model::User>) -> Result<impl Responder> {
    let conn = db.get().unwrap();
    let new_user = model::User {
        email: item.email.to_string(),
    };
    diesel::insert_into(schema::users::dsl::users)
        .values(&new_user)
        .execute(&conn)
        .expect("Error saving new post");

    Ok(HttpResponse::Created().body("get ok"))
}
```

```rust:src/main.rs
// PUT API
#[put("/users/{id}")]
async fn put(
    db: web::Data<db::Pool>,
    path: web::Path<i32>,
    item: web::Json<model::User>,
) -> Result<impl Responder> {
    let id = path.into_inner();
    let conn = db.get().unwrap();
    let target = schema::users::dsl::users.filter(schema::users::dsl::id.eq(id));

    diesel::update(target)
        .set(schema::users::dsl::email.eq(item.email.to_string()))
        .execute(&conn)
        .expect("Error updating new post");

    Ok(HttpResponse::Created().body("update ok"))
}
```

```rust:src/main.rs
// DELETE API
#[delete("/users/{id}")]
async fn destroy(db: web::Data<db::Pool>, path: web::Path<i32>) -> Result<impl Responder> {
    let id = path.into_inner();
    let conn = db.get().unwrap();
    let target = schema::users::dsl::users.filter(schema::users::dsl::id.eq(id));

    diesel::delete(target)
        .execute(&conn)
        .expect("Error deleting new post");

    Ok(HttpResponse::Created().body("Delete ok"))
}
```

エンドポイントを追加したのち、HTTPServerにserviceを渡せば動くようになるかと思います。

```rust:src/main.rs

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let pool = db::establish_connection();

    HttpServer::new(move || {
        App::new()
            .app_data(Data::new(pool.clone()))
            .service(get)
            .service(post)
            .service(put)
            .service(destroy)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}
```

`cargo run`を叩いてcURLやPostmanでリクエストを送ってみてください。




## Rustで開発してみての感想
- javascript→typescriptに移行した時のような型定義で手こずる感覚がある。(仕事では主にpythonを使用しております)
- エコシステムが既に強力、リンターやフォーマットが豊富にあり開発体験はかなりいい。
- 日本語のドキュメントは少なめ。特にweb APIの作成に関しては。stackoverflowなどから情報をかき集めて作成した。
- パフォーマンス、堅牢性を考えるとこれからweb APIとしてももっと使われることになりそう。


質問疑問等あればコメントください！
Rust日本語の記事が少なかったのでこれからもたくさん情報発信していこうかと思います。
今回使用したコードはgithubにあげておいたのでぜひ参考にしてみてください。

https://github.com/c0ridrew/rust-practice