nest_structを使ってネストした構造体を定義する
モチベーション
例えばgolangであれば匿名構造体を使って、簡潔に定義することができます。
type User struct {
ID int `json:"id"`
Profile struct {
Name string `json:"name"`
Bio string `json:"bio"`
} `json:"profile"`
Stats struct {
FollowerCount int `json:"follower_count"`
FolloweeCount int `json:"followee_count"`
} `json:"stats"`
}
rustでもこんな感じにネストした構造体を定義したいです。
rustでは nest_struct を使うことで良い感じに解決することができました。
nest_structについて紹介する日本語の記事が無さそうなので、紹介記事を書きます。
nest_structのバージョンは0.5.5を前提とします。
nest_struct
名前の通りネストした構造体を定義するためのマクロを提供してくれます。
前述のgolangと同等の構造体を定義すると以下のようになります。
use nest_struct::nest_struct;
#[nest_struct]
struct User {
id: String,
profile: nest! {
name: String,
bio: String,
},
stats: nest! {
follower_count: i64,
followee_count: i64,
},
}
cargo expand
でマクロを展開すると以下のようになりました。
struct UserProfile {
name: String,
bio: String,
}
struct UserStats {
follower_count: i64,
followee_count: i64,
}
struct User {
id: String,
profile: UserProfile,
stats: UserStats,
}
あるいは構造体の名前を自分で指定することもできます。
#[nest_struct]
struct User {
id: String,
profile: Profile! {
name: String,
bio: String,
},
stats: Stats! {
follower_count: i64,
followee_count: i64,
},
}
↓ cargo expand
struct Profile {
name: String,
bio: String,
}
struct Stats {
follower_count: i64,
followee_count: i64,
}
struct User {
id: String,
profile: Profile,
stats: Stats,
}
derive
今回やりたいことはserdeを使うことです。
nest_structはルートの構造体のderiveを継承してくれるので楽ですね。
#[nest_struct]
#[derive(Debug, Clone, Serialize, Deserialize)]
struct User {
id: String,
profile: nest! {
name: String,
bio: String,
},
stats: nest! {
follower_count: i64,
followee_count: i64,
},
}
↓ 展開後のイメージ(実際には #[derive()]
も展開されて impl
のコードになります)
#[derive(Debug, Clone, Serialize, Deserialize)]
struct UserProfile {
name: String,
bio: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct UserStats {
follower_count: i64,
followee_count: i64
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct User {
id: String,
profile: UserProfile,
stats: UserStats,
User, UserProfile, UserStatsのそれぞれに Debug
Clone
Serialize
Deserialize
を実装したコードが生成されました。
Vec, Option
Vec
や Option
にしたいことも多いでしょう。
やってみたら普通にできました。すごい 👏
#[nest_struct]
#[derive(Debug, Clone, Serialize, Deserialize)]
struct User {
id: String,
profile: Vec<nest! {
name: String,
bio: String,
}>,
stats: Option<Stats! {
follower_count: i64,
followee_count: i64,
}>,
}
↓ 展開後のイメージ
#[derive(Debug, Clone, Serialize, Deserialize)]
struct UserProfile {
name: String,
bio: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Stats {
follower_count: i64,
followee_count: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct User {
id: String,
profile: Vec<UserProfile>,
stats: Option<Stats>,
}
enum, attribute
enum
や #[serde()]
もいけます。
derive
と同様に #[serde()]
も継承されます。
#[nest_struct]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
struct User {
id: String,
theme: Option<nest! {
Light,
Dark,
Custom {
colors: nest! {
#[serde(rename = "bg")]
background: String,
text: String,
}
},
}>,
}
↓ 展開後のイメージ
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
struct UserThemeCustomColors {
#[serde(rename = "bg")]
background: String,
text: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
enum UserTheme {
Light,
Dark,
Custom { colors: UserThemeCustomColors },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
struct User {
id: String,
theme: Option<UserTheme>,
}
柔軟性
ルートの #[serde()]
を継承したくない時や、内部の構造体にだけ追加の derive
を定義したい時もあると思います。
そういう時は nest!
の中に struct
/ enum
キーワードで定義します。
#[nest_struct]
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "UPPERCASE")]
struct User {
id: String,
theme: nest! {
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
enum Theme {
Light,
Dark,
Custom {
colors: nest! {
#[serde(rename = "bg")]
background: String,
text: String,
},
},
}
},
}
↓ 展開後のイメージ
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
struct ThemeCustomColors {
#[serde(rename = "bg")]
background: String,
text: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
enum Theme {
Light,
Dark,
Custom {
colors: ThemeCustomColors,
},
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "UPPERCASE")]
struct User {
id: String,
theme: UserTheme,
}
この形式の場合、 ThemeCustomColor
は Theme
のattribute( derive
や #[serde()]
)を継承することに注意してください。
「ルートのものを継承する」というより、正確には「最も近い祖先の struct
/ enum
を書いて定義されたものを継承する」という風に思えます。
ここまでできれば基本的なユースケースでは十分でないでしょうか。
ジェネリクスとライフタイム
serdeと併用する時に使うことはあまりないでしょうが、ジェネリクスやライフタイムにも対応しているようです。
例については公式のテストケースを参照してください。(ちゃんと試してない)
Discussion