🚶

足を止めて見る #4 〜 RustのSerdeクレート(1) 〜

に公開2

足を止めて見よう

足を止めて見ようシリーズの4つ目です。

前回は TryFrom トレイトについてでした。

今回は、Rustの定番クレートの1つと言ってもいい serde クレートを足を止めて見てみます。

serde クレートとは

とにかくまずは docs.rsserde クレートを見に行きましょう。

https://docs.rs/serde/latest/serde/

「Rustの構造体をシリアライズしたりデシリアライズするのに便利なフレームワークです」との紹介が冒頭にあります。

具体的に言うと、Rustの構造体をたとえばJSON形式にシリアライズしたり、たとえばCSV形式のデータをRustの構造体にデシリアライズする際に有用です。

serde クレートをインストール

まずはインストールですね。

$ cargo add serde -F derive
$ cargo add serde_json

Cargo.toml に以下の依存関係が追加されました。

[dependencies]
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.143"

-F derive としたことで derive という crate feature が追加されました。こちらについては後ほど深ぼってみます。

serde_json というクレートも追加しています。これは JSON 形式を扱う場合に利用します。

serde クレートを使ってみる

今回は Person という構造体を用意して、それをJSON形式にシリアライズし、さらにそれをデシリアライズして Person 構造体に戻すことを試してみます。

#[derive(serde::Serialize, serde::Deserialize)]
struct Person {
    nickname: String,
    age: u8,
}

fn main() {
    let person = Person {
        nickname: String::from("タロー"),
        age: 30,
    };

    let serialized = serde_json::to_string(&person).unwrap();
    let deserialized: Person = serde_json::from_str(&serialized).unwrap();

    assert_eq!(serialized, "{\"nickname\":\"タロー\",\"age\":30}");
    assert_eq!(person.nickname, deserialized.nickname);
    assert_eq!(person.age, deserialized.age);
}

このように書けました。実行するとこちらは成功します。

serde_json::to_string にPerson構造体のデータを渡すと、たしかに以下のようなJSONになります。

{
    "nickname": "タロー",
    "age": 30
}

またこのJSONを serde_json::from_str に渡すと、たしかにPerson構造体になります。

なんとなく雰囲気は掴めました。

もう一段だけ深ぼってみる #1

serde はどのようにしてPerson構造体のデータをJSONに変換できたのでしょうか?

うっすら気付いていると思いますが #[derive(serde::Serialize)] のおかげです。

試しに #[derive(serde::Serialize, serde::Deserialize)] の行をコメントアウトして保存するとコンパイルエラーになり、次のようなエラーメッセージが表示されます。

the trait bound `Person: serde::ser::Serialize` is not satisfied

つまり Person構造体に serde::ser::Serializeimpl しておくれ、と怒られています。

だとすると #[derive(serde::Serialize)] を書けば serde::ser::Serialize が自動的に実装されると理解できます。

いつものようにソースコードを見てみます。

https://docs.rs/serde_derive/1.0.219/src/serde_derive/lib.rs.html#92

#[proc_macro_derive(Serialize, attributes(serde))]
pub fn derive_serialize(input: TokenStream) -> TokenStream {
    let mut input = parse_macro_input!(input as DeriveInput);
    ser::expand_derive_serialize(&mut input)
        .unwrap_or_else(syn::Error::into_compile_error)
        .into()
}

derive宣言的手続き的マクロと呼ばれているもので、TRPL19章_高度な機能にも紹介があります。(難しいですね)

詳しいことは分かりませんが、とりあえず ser modの expand_derive_serialize 関連関数が呼ばれています。

https://docs.rs/serde_derive/1.0.219/src/serde_derive/ser.rs.html#11-61

pub fn expand_derive_serialize(input: &mut syn::DeriveInput) -> syn::Result<TokenStream> {
    // { 途中省略 }
    quote! {
        #[automatically_derived]
        impl #impl_generics #ident #ty_generics #where_clause {
            #vis fn serialize<__S>(__self: &#remote #ty_generics, __serializer: __S) -> #serde::__private::Result<__S::Ok, __S::Error>
            where
                __S: #serde::Serializer,
            {
                #used
                #body
            }
        }
    }
    // { 途中省略 }
}

expand_derive_serialize 関連関数をみてみると、何やら impl ブロックを生成しているような記述がありました。きっとこのあたりの記述により、自動的に serde::ser::Serialize が実装される状態にしてくれているのでしょうね。

もう一段だけ深ぼってみる #2

cargo add serde -F derive とすることで dervie という crate feature が追加されました。こちらは一体何なのでしょうか。

もうお分かりかと思いますが、先ほどの dervie マクロを利用するためには、このfeatureが必要とのことです。

https://docs.rs/serde/latest/serde/derive.Serialize.html

今回は一旦そうなんだくらいで終わりにしようと思います。

振り返り

今回は serde クレートを改めて足を止めて見てみました。

これまでのシリーズと比較すると crate featurederiveマクロ など、やっぱり難易度が一段と上がってきました。

serde に関しては、もう少し足を止めてみた方が良さそうなので、もう何個か調べて見ようと思います。

その他

今回書いたRustのコードはこのリポジトリで制作しています。

https://github.com/kenshuhori/rust/tree/main/workspace/dive_4_serde_crate

GitHubで編集を提案
ドクターメイト

Discussion

kanaruskanarus

derive は宣言的マクロと呼ばれているもので、TRPL19章_高度な機能にも紹介があります。(難しいですね)

derive は手続き的マクロ (procedural macro, proc macro) の一種です

宣言的マクロは

macro_rules! sum {
    ($n:expr $(, $rest:expr)*) => {
        $n $(+ $rest)*
    };
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=e93a5b087709dec9947f09bcd4a96c47

みたいに macro_rules! で定義するやつです

( TRPL19章_高度な機能にもそう書いてあります…… )

ホリケンシュウホリケンシュウ

ありがとうございます!修正しました。
弊社の他記事にもコメント頂いていたりで励みになります!