Open34

Rust 学ぶ

zaruzaru

Rust の基本構文は、昔に少しだけ学んだ。今は完全に忘れている。

とりあえず今回はアプリを作りながら学んでみる。
適当に Web フレームワークを選定。Rocket にしてみる。理由は特にない。どういう素性なんだろう。

https://rocket.rs/

Rust は asdf で管理しているので最新にしてみる。v1.70.0 をインストール。

asdf install rust 1.70.0
asdf global rust 1.70.0

チュートリアルを見ると Shuttle というサービスを使って Rust アプリを動かすことができるらしい。Ruby でいうところの Heroku、Next でいうところの Vercel だろうか。

https://www.shuttle.rs/

Shuttle を CLI 操作?できるライブラリをインストールしてみる。

cargo install cargo-shuttle

Rust のバージョンが古いと (1.69.0) 普通にエラーになる。

error[E0658]: use of unstable library feature 'is_some_and'

is_some_and は 1.70.0 から追加されたらしい。

https://doc.rust-lang.org/std/option/enum.Option.html#method.is_some_and

Rust のライブラリバージョン依存ってどういう仕組みなんだろ。インストールされている Rust バージョンに合わせて cargo が適切なバージョンをインストールしてくれるわけじゃないのか?

とりあえず無事に cargo-shuttle インストールできた。PATH に追加しろというので fish の PATH 追加をする。

 fish_add_path /Users/zaru/.cargo/bin
zaruzaru

え… rustup っていう Rust インストールを管理する公式ツールがあるのか?

https://rust-lang.github.io/rustup/

公式なら asdf 使わずに rustup を素直に使う方が良さそう。

そして cargo install と rustup component add は同じようにライブラリをインストールするけど、挙動が違う様子…。

  • rustup component add はビルド済みをインストール
  • cargo install はソースをダウンロードして手元でビルド

どちらが良いのかはよく分からなかった…。検索をすると cargo でインストールするドキュメントばかりだが。

zaruzaru

Rust バージョン管理は asdf よりも公式の rustup の方が良いと言うことなので入れ替える。

asdf plugin remove rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
fish_add_path /Users/zaru/.cargo/bin

これでインストール完了。

rustc --version
rustc 1.70.0 (90c541806 2023-05-31)
zaruzaru

cargo コマンドでプロジェクトを作成できるらしい。

cargo new rocket_example --bin

オプションの --bin は、 src/main.rs というファイルを自動作成するみたい。他に --lib というオプションがある。違いは bin は通常の実行バイナリを作る。 lib はライブラリを作る目的のよう。

cargo new すると、ディレクトリとともにファイルが自動作成される。

tree       
                                                                                                                                                                                                             master
.
├── Cargo.toml
└── src
    └── main.rs
zaruzaru

メインエディタの IntelliJ で該当ディレクトリを開く。

すると Cargo Check という機能があることに気がついた。なんだこれ。軽く調べてみると、ライブラリやパッケージが壊れていないかを事前にチェックしてくれるものらしい。 cargo build で生成物を作るよりも手前で気がつける。かつキャッシュも聞くので便利…らしい。どれだけ壊れるのか体感がないので便利なのかは今は不明。

IntelliJ の Cargo Check 設定欄を見ると、Clippy というツールも選べる。調べると Clippy はリンターみたい。Cargo Check と比較すると、よりリンターらしいチェックをしてくれると言うことのようだ。どちらかしか有効にできないので Clippy を選ぶことにした。

そのほかにもフォーマッターなどがあるらしい。

https://doc.rust-jp.rs/book-ja/appendix-04-useful-development-tools.html

とりあえず IntelliJ で rustfmt を有効にしてみた。これがデファクトスタンダードなフォーマットと言うことだろうか。

zaruzaru

もろもろいじっていたら Cargo.lock ファイルが生成されていた。よくあるパッケージのバージョン管理ファイルだろう。git で管理する。ライブラリ目的だと管理しないらしい。

# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3

[[package]]
name = "rocket_example"
version = "0.1.0"
zaruzaru

Rocket を使うために Cargo.toml を編集する。自分で書いても良いらしいが、さすがに手書きはつらい。普通に cargo add コマンドで追加できる様子。

cargo add rocket

実行すると Cargo.toml に以下が追加された。

[dependencies]
rocket = "0.4.11"

チュートリアル記事を見ると、rocket_dyn_templates も使うと良いらしい。これは動的なテンプレートをサポートしてくれるライブラリっぽい。示されている Cargo.toml を見ると、謎の記法があった。 dependencies の後にドットでライブラリ名がつながっている。どういうことだろう。

[dependencies.rocket_dyn_templates]
version = "0.1.0-rc.2"
features = ["handlebars","tera"]

普通に cargo add rocket_dyn_templates すると以下のように普通に書かれる。

[dependencies]
rocket = "0.4.11"
rocket_dyn_templates = "0.1.0-rc.3"

でも rocket_dyn_templates を使った記事を調べると [dependencies.rocket_dyn_templates] の書かれ方をしている。

リファレンスを確認すると、パッケージの特定機能だけを使いたい場合は、このようにドットを付けて書くらしい。なるほど。

https://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/share/doc/rust/html/cargo/reference/specifying-dependencies.html#choosing-features

[dependencies.awesome]
version = "1.3.5"
default-features = false # do not include the default features, and optionally
                         # cherry-pick individual features
features = ["secure-password", "civet"]
zaruzaru

Rocket 公式ドキュメントのコードを実行してみる。

#[macro_use]
extern crate rocket;

#[get("/")]
fn index() -> &'static str {
    "Hello, world!"
}

#[launch]
fn rocket() -> _ {
    rocket::build().mount("/", routes![index])
}

IntelliJ のエディタでは main() 関数がないよ、と警告されている。とりあえず無視して cargo run する。

cargo run
# 略 : いろいろパッケージをビルドするログが流れる

error: failed to run custom build command for `pear_codegen v0.1.5`

Caused by:
  process didn't exit successfully: `/Users/zaru/Sites/rocket_example/target/debug/build/pear_codegen-738a2be10713487d/build-script-build` (exit status: 101)
  --- stderr
  Error: Pear requires a 'dev' or 'nightly' version of rustc.
  Installed version: 1.70.0 (2023-05-31)
  Minimum required:  1.31.0-nightly (2018-10-05)
  thread 'main' panicked at 'Aborting compilation due to incompatible compiler.', /Users/zaru/.cargo/registry/src/index.crates.io-6f17d22bba15001f/pear_codegen-0.1.5/build.rs:24:13
  note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
warning: build failed, waiting for other jobs to finish...

エラーになった。Error: Pear requires a 'dev' or 'nightly' version of rustc. と書かれているので、どうやら dev / nightly な rustc を使ってくれと言うことらしい。安定リリース版じゃないものを使ってくれと言うのは… Rust 的にはよくあることなんだろうか?

ドキュメントを見ると、基本的には安定版を使うっぽい。単に Rocket が依存するパッケージのバージョンが nightly を求めているっぽい。ここら辺のバージョンアップの肌感がよくわからない。 nightly に依存するならそのパッケージも nightly としてリリースされていて欲しいが…。

https://doc.rust-jp.rs/book-ja/appendix-07-nightly-rust.html

zaruzaru

Nightly な Rust インストールをする。

rustup install nightly

バージョンを確認してみる。

rustup run nightly rustc --version                                                                                                                                                                                    
rustc 1.72.0-nightly (04075b320 2023-06-22)

デフォルトを nightly にする。

rustup default nightly
rustc --version
rustc 1.72.0-nightly (04075b320 2023-06-22)
zaruzaru

改めて cargo run して Rokcet サンプルを動かす。

error: at least one of "tera" or "handlebars" features must be enabled
   --> /Users/zaru/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rocket_dyn_templates-0.1.0-rc.3/src/lib.rs:142:1
    |
142 | compile_error!("at least one of \"tera\" or \"handlebars\" features must be enabled");
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: could not compile `rocket_dyn_templates` (lib) due to previous error
warning: build failed, waiting for other jobs to finish...

次は別のエラーが出た。 rocket_dyn_templates の機能を有効にしろとのこと。もしかして前に書いた Cargo.toml で明示的に機能を書く必要があるのか? よく分からないが、とりあえず以下のように修正をした。

[dependencies]
rocket = "0.4.11"

[dependencies.rocket_dyn_templates]
version = "0.1.0-rc.3"
features = ["handlebars", "tera"]
zaruzaru

さらに改めて cargo run して Rocket サンプルを動かす。

error: cannot find attribute `launch` in this scope
 --> src/main.rs:9:3
  |
9 | #[launch]
  |   ^^^^^^

error[E0425]: cannot find function `build` in crate `rocket`
  --> src/main.rs:11:13
   |
11 |     rocket::build().mount("/", routes![index])
   |             ^^^^^ not found in `rocket`

error[E0658]: `macro` is experimental
 --> src/main.rs:4:1
  |
4 | #[get("/")]
  | ^^^^^^^^^^^
  |
  = note: see issue #39412 <https://github.com/rust-lang/rust/issues/39412> for more information
  = help: add `#![feature(decl_macro)]` to the crate attributes to enable
  = note: this error originates in the attribute macro `get` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0601]: `main` function not found in crate `rocket_example`
  --> src/main.rs:12:2
   |
12 | }
   |  ^ consider adding a `main` function to `src/main.rs`

error[E0121]: the placeholder `_` is not allowed within types on item signatures for return types
  --> src/main.rs:10:16
   |
10 | fn rocket() -> _ {
   |                ^ not allowed in type signatures

Some errors have detailed explanations: E0121, E0425, E0601, E0658.
For more information about an error, try `rustc --explain E0121`.
error: could not compile `rocket_example` (bin "rocket_example") due to 5 previous errors

別のエラーが出た。エラーが変わるというのは進歩している証拠だ。

Rocket のドキュメントをよく見たら使っている Rocket のバージョンが全然異なる。今、自分が使っているのが rocket = "0.4.11" だが、ドキュメントには rocket = "=0.5.0-rc.3"。RC バージョンを積極的にドキュメントで案内している…。やはり Rust はこういう攻めな姿勢でも安定しているよ的な文化なのか?

気を取り直して Rocket のバージョンを入れ替える。再度 cargo run すると無事 Web サーバが起動できた。

🔧 Configured for debug.
   >> address: 127.0.0.1
   >> port: 8000
   >> workers: 10
   >> max blocking threads: 512
   >> ident: Rocket
   >> IP header: X-Real-IP
   >> limits: bytes = 8KiB, data-form = 2MiB, file = 1MiB, form = 32KiB, json = 1MiB, msgpack = 1MiB, string = 8KiB
   >> temp dir: /var/folders/j1/11sx50cj3ws85n8tp99thx8r0000gn/T/
   >> http/2: true
   >> keep-alive: 5s
   >> tls: disabled
   >> shutdown: ctrlc = true, force = true, signals = [SIGTERM], grace = 2s, mercy = 3s
   >> log level: normal
   >> cli colors: true
📬 Routes:
   >> (index) GET /
📡 Fairings:
   >> Shield (liftoff, response, singleton)
🛡️ Shield:
   >> X-Content-Type-Options: nosniff
   >> Permissions-Policy: interest-cohort=()
   >> X-Frame-Options: SAMEORIGIN
🚀 Rocket has launched from http://127.0.0.1:8000
zaruzaru

サンプルコードの先頭に書かれている以下のコードは Rocket を使うよと言う宣言。 extern crate で外部パッケージを読み込むことができる。さらに #[macro_use] と宣言することでマクロが使える。

#[macro_use]
extern crate rocket;

しかし、調べてみると 2018 以降は use で書くことができる。マクロを使わないなら、そもそも extern crate 宣言自体が不要で何もしなくても良いらしい。

use rocket::get;
use rocket::launch;
use rocket::routes;

どちらが良いのかは不明。使いたいマクロが大量にある場合は、都度 use で書くよりも、素直に extern crate する方が楽っぽい。

公式ドキュメントでも言及されていた。
https://rocket.rs/v0.5-rc/guide/overview/

Note: We prefer #[macro_use], but you may prefer explicit imports.
Throughout this guide and the majority of Rocket's documentation, we import rocket explicitly with #[macro_use] even though the Rust 2018 edition makes explicitly importing crates optional. However, explicitly importing with #[macro_use] imports macros globally, allowing you to use Rocket's macros anywhere in your application without importing them explicitly.

You may instead prefer to import macros explicitly or refer to them with absolute paths: use rocket::get; or #[rocket::get].

zaruzaru

Rocket のサンプルアプリを動かしただけではライブリロードは動かない様子。そもそもサポートされているのだろうか?

調べてみると cargo-makecargo-watch を使うと都度ビルドし直してくれるっぽい。

zaruzaru

Rocket で Template を使ってみる。 rocket_dyn_template で外部ファイル View が使える。

use rocket_dyn_templates::{context, Template};

#[get("/")]
fn sample() -> Template {
    Template::render(
        "sample",
        context! {
            foo: 123,
        },
    )
}

#[launch]
fn rocket() -> _ {
    rocket::build()
        .mount("/sample", routes![sample])
        .attach(Template::fairing())
}

context オブジェクトでテンプレートに渡すパラメータを設定できる。今回のように使い捨ての書き方もできるし、ちゃんと型を指定して渡すこともできる。

View ファイルは xxx.html.tera と命名し、ルート /templates ディレクトリに保存する。

<!DOCTYPE html>
<html>
  <head>
    <title>Templating Example</title>
  </head>
  <body>
     Argument :{{ foo }}
  </body>
</html>

変数展開は {{ }} で行う。

なお、View ファイルに関しては Live Reloading がサポートされているため、View ファイル修正後、ブラウザを再読み込みすると反映されている。これは嬉しい。

zaruzaru

tera というのは rocket_dyn_templates 自身の機能ではなく、 tera というパッケージの様子。これがテンプレートエンジン。tera 以外にも handlebars というテンプレートエンジンもサポートされている。

どちらもよくあるテンプレート記法。どちらが良いかは使い込んでみないと分からなそう。とりあえず tera にしたいが… IntelliJ には tera のプラグインがない…。handlebars はあるっぽい。

VS Code には tera の拡張機能があるな…。

Crate のサイトでダウンロード人気ランキングを見てみる。

1位が tinytemplate で、2位が handlebars、3位が tera だった。

エディタサポートがされていないと厳しいので、ひとまず tera ではなく handlebars にすることとした。

tera から handlebars への切り替えはファイル名を変更するだけで済んだ。

sample.html.tera -> sample.html.hbs

zaruzaru

試しに POST するサンプルを作ってみる。

use rocket::form::Form;

#[derive(FromForm)]
struct SampleForm {
    name: String,
}

#[post("/", data = "<sample_form>")]
fn post_sample(sample_form: Form<SampleForm>) -> Template {
    Template::render(
        "sample",
        context! {
            foo: 123,
            name: sample_form.name.to_string(),
        },
    )
}

受け取るパラメータを Struct 構造体で定義して、それを引数として受け取る。

    <form action="/sample" method="post">
      <input type="text" name="name">
      <button type="submit">送信</button>
    </form>
    <p>入力したのは {{ name }} です</p>
zaruzaru

Rocket のルーティングは以下のようにまとめられる。

rocket::build()
  .mount("/", routes![foo])
  .mount("/", routes![bar])
rocket::build()
  .mount("/", routes![foo, bar])
zaruzaru

IntelliJ で main function not found という警告が出る。警告を出さないようにする改善要望 Issue が立ち上がっている。

https://github.com/intellij-rust/intellij-rust/issues/5975

nightly モードで実験的機能を On にすれば警告が出なくなるらしいが、うまく反映されなかった。

zaruzaru

Rocket で POST 受け取るパラメータは struct で定義できる。

#[derive(FromForm)]
struct SampleForm {
    name: String,
}

#[post("/", data = "<sample_form>")]
fn post_sample(sample_form: Form<SampleForm>) -> Template {
}

derive マクロで FromForm trait を利用している。Rust の trait は、共通の振る舞いを定義して利用することができる機能。他言語の interface に似ている。trait の機能性はわかったが、derive マクロと一緒に使うことでどういう機能が提供されているのかは今は不明。

Rocket のコードを見ると以下のコメントが記載されていた。

Each field type is required to implement FromForm.

derive マクロを使うことで、 struct で定義している各フィールドは FromForm trait を実装する必要が出てくる。つまり、自動で trait を適用してくれるマクロ。FromFrom trait のコードを読むと、初期値や値の変更などのメソッドシグネチャが定義されていた。中身はまだよく分からない。

zaruzaru

Rust で Web アプリを作る際の View 変数、型どうなってるの問題。

Rocket + handlebars で構築しているが xxx.html.hbs ファイルで存在していない変数を参照しても、当然エラーにはならない。しかし開発体験としては View ファイルをエディタで編集する際にひんてぃんぐして欲しいし、エラーになって欲しい。

例えば Next.js のような TypeScript で全体をフロント・サーバサイドを作っている場合は検知可能。

Rust の場合はどのようにすると良いのだろうか?

zaruzaru

Yew ライブラリを使うことで Next.js のような(あるいは React / JSX か)体験ができる?

https://yew.rs/

playground で簡単な例を試してみたが、Rust ファイルの中で HTML を JSX のように書けるため、当然のことながら View の変数問題が解決される。存在しない変数は利用できないし、型もちゃんと明確になる。

悪くない選択肢のように感じるが… Yew のコンセプトをまだ理解していない。

zaruzaru

Yew を試すためにドキュメントを参照しながらサンプルアプリを作ってみる。

https://yew.rs/docs/getting-started/build-a-sample-app

まず cargo-generate をインストールする。GitHub のリポジトリからプロジェクトを作成することができるツールの様子。

cargo install cargo-generate

何回か cargo でインストールして思ったけど…依存 crate をぜんぶビルドするから時間がかかるな…。

cargo-generate を入れたら Yew のテンプレートを使ってプロジェクトを作成する。

cargo generate --git https://github.com/yewstack/yew-trunk-minimal-template

実行するとプロジェクト名を聞かれ、入力したらディレクトリとファイルが作成される。

cargo run

実行したら WASM がビルドできないよとエラーが出た。

thread 'main' panicked at 'cannot call wasm-bindgen imported functions on non-wasm targets', /Users/zaru/.cargo/registry/src/index.crates.io-6f17d22bba15001f/js-sys-0.3.60/src/lib.rs:5520:9

プロジェクトの README にびるどターゲットに WASM を追加しろと書いてあったので以下を実行する。

rustup target add wasm32-unknown-unknown

しかし結果は変わらず。そういうことではなかった。 wasm-bindgen がないというエラーだったか。

cargo install trunk wasm-bindgen-cli

と思ったけど、そもそも cargo run じゃなかった。ドキュメントでは cargo run だけど、README は trunk serve だった。

trunk serve

サーバは起動しそうだったけど、今度は違うエラーが出た。

error
error from HTML pipeline

Caused by:
    0: error from asset pipeline
    1: failed downloading release archive
    2: error downloading archive file: 404
       https://github.com/rustwasm/wasm-bindgen/releases/download/0.2.83/wasm-bindgen-0.2.83-aarch64-apple-darwin.tar.gz

うーむ…存在しない。テンプレートプロジェクトが参照している wasm-bindgen は 0.2.83 だが、このバージョンはまだ AppleSillicon 用のバイナリは存在していなかった様子。

cargo update

プロジェクトの crate をアップデートして wasm-bindgen を 0.2.87 にすると trunk serve で正常に起動ができた。

zaruzaru

Yew で JSX 風に HTML を書けるけど、IntelliJ も VS Code もまともにサポートしているわけじゃないから…書き味はぜんぜん良くない。GitHub Copilot を使っていれば推測して、ある程度は補完はしてくれるんだけど、そういうことじゃない。

zaruzaru

Rocket で JSON をレスポンスするコードを書く。

JSON を扱うために以下の機能を読み込む。

use rocket::serde::json::Json;
use rocket::serde::Serialize;

// 以下のようにも書ける
use rocket::serde::{Serialize, json::Json};

すると、以下のようなエラーが出た。

unresolved import `rocket::serde::json` [E0432] could not find `json` in `serde`

調べると Cargo.toml で features = ["json"] を指定する必要がある様子。

[dependencies]
rocket = "=0.5.0-rc.3"

# 以下に書き換え

[dependencies.rocket]
version = "0.5.0-rc.3"
features = ["json"]
zaruzaru

JSON を返す実装例。

#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
struct SampleJson {
    foo: String,
    name: String,
}

#[get("/sample_json")]
fn sample_json() -> Json<SampleJson> {
    Json(SampleJson {
        foo: "Hello, world!".to_string(),
        name: "Taro".to_string(),
    })
}

serde とは Rust のシリアライズをサポートする Crate 。要は Struct データを JSON に変換することができる。serde(crate = "rocket::serde") として Rocket の serde を使うようにしている?

zaruzaru

エディタ上では Unresolved reference: Json という警告が出ているが、特に問題なくビルドできる。なんだろうこれ。features トグルで有効にした JSON 機能がエディタで正しく認識されていない可能性が高い。

うーん…

zaruzaru

Rocket + sqlx で MySQL なデータを表示するアプリを作ろうとしたけど挫折

sqlx::query! が動かせない…

zaruzaru

最初から理解していない複数の Crate を使いこなそうとしたのが間違いだった。まずは sqlx 自体を使ってみることとする。

cargo new sqlx-sample --bin

https://github.com/launchbadge/sqlx

公式の導入方法を確認すると、tokioasync-std の違い、さらに TLS かどうかなどいくつかの方式が存在する様子。

# Cargo.toml
[dependencies]
# PICK ONE OF THE FOLLOWING:

# tokio (no TLS)
sqlx = { version = "0.7", features = [ "runtime-tokio" ] }
# tokio + native-tls
sqlx = { version = "0.7", features = [ "runtime-tokio", "tls-native" ] }
# tokio + rustls
sqlx = { version = "0.7", features = [ "runtime-tokio", "tls-rustls" ] }

# async-std (no TLS)
sqlx = { version = "0.7", features = [ "runtime-async-std" ] }
# async-std + native-tls
sqlx = { version = "0.7", features = [ "runtime-async-std", "tls-native" ] }
# async-std + rustls
sqlx = { version = "0.7", features = [ "runtime-async-std", "tls-rustls" ] }

大きく2つ分かれている。

  • 非同期ライブラリ : async-std, tokio, and actix
  • TLS ライブラリ : native-tls and rustls
    • rustls 一瞬 result かと思って意味不明だった

これらライブラリの組み合わせを自由にやっていいらしい。

とりあえず検証なので tokio のみでやってみる。

[package]
name = "sqlx-sample"
version = "0.1.0"
edition = "2021"

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

[dependencies]
sqlx = { version = "0.7", features = [ "runtime-tokio", "mysql" ] }
tokio = { version = "1.29.1", features = ["full"] }
cargo run

sqlx インストール自体は無事完了。

zaruzaru
  • 正式な表記は SQLx だった
  • SQLx は ORM ではないと README に書かれている
  • ORM を使いたければ ormx や SeaORM を使ってくれ
zaruzaru

ドキュメントにあるサンプルコードを元に1件取得して表示させてみる。

use sqlx::mysql::MySqlPoolOptions;

#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
    let pool = MySqlPoolOptions::new()
        .max_connections(5)
        .connect("mysql://root:password@127.0.0.1/employees").await?;

    // Make a simple query to return the given parameter (use a question mark `?` instead of `$1` for MySQL)
    let row: (i64,) = sqlx::query_as("SELECT * FROM employees")
        .fetch_one(&pool).await?;

    println!("{}", row.0);

    Ok(())
}
zaruzaru

他の列も取得しようとしたところ、let row: (i64,) = sqlx::query_as の受け取る部分で型を指定する必要があった。サンプルのテーブルが以下の構造。

desc employees;
+------------+---------------+------+-----+---------+-------+
| Field      | Type          | Null | Key | Default | Extra |
+------------+---------------+------+-----+---------+-------+
| emp_no     | int           | NO   | PRI | NULL    |       |
| birth_date | date          | NO   |     | NULL    |       |
| first_name | varchar(14)   | NO   |     | NULL    |       |
| last_name  | varchar(16)   | NO   |     | NULL    |       |
| gender     | enum('M','F') | NO   |     | NULL    |       |
| hire_date  | date          | NO   |     | NULL    |       |
+------------+---------------+------+-----+---------+-------+

datevarchar の2つを受け取ってみる。date は chrono という Crate を使うらしい。Cargo.toml に追加する。

[dependencies]
sqlx = { version = "0.7", features = [ "runtime-tokio", "mysql", "chrono" ] }
tokio = { version = "1.29.1", features = ["full"] }
chrono = { version = "0.4.26" }

あとは型を指定して表示させる。

    let row: (i64,chrono::NaiveDate,String,) = sqlx::query_as("SELECT * FROM employees")
        .fetch_one(&pool).await?;

    println!("{}, {}, {}", row.0, row.1, row.2);
zaruzaru

Struct で定義して渡すこともできる。基本はこちらの使い方か。

use sqlx::mysql::MySqlPoolOptions;

#[derive(sqlx::FromRow)]
struct Employee { emp_no: i64, birth_date: chrono::NaiveDate, first_name: String, last_name: String }

#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
    let pool = MySqlPoolOptions::new()
        .max_connections(5)
        .connect("mysql://root:password@127.0.0.1/employees").await?;

    // Make a simple query to return the given parameter (use a question mark `?` instead of `$1` for MySQL)
    let row: Employee = sqlx::query_as("SELECT * FROM employees")
        .fetch_one(&pool).await?;

    println!("{}, {}, {}", row.emp_no, row.birth_date, row.first_name);

    Ok(())
}