Deriving via for Rust
どうも、みたまです
……春。
桜咲き乱れる出会いと別れのその季節。
Rust で Haskell の DerivingVia のようなことができるライブラリを作りました。
……お願いします。
お願いします、どうかリポジトリにスターをつけてください。
普通の使い方
普通の derive マクロのように使うこともできます。
ただし普通の derive マクロと違い、DerivingVia では #[deriving]
アトリビュートの中に生成したい impl のトレイトを書きます。
#[derive(DerivingVia)]
#[deriving(From, Display)]
pub struct D(i32);
ジェネリクスも使えます。
#[derive(DerivingVia)]
#[deriving(From, Display)]
pub struct D<T: Display>(T);
newtype pattern も使えます。
newtype pattern を使う場合は、#[underlying]
をつけることが必須となります。
#[underlying]
をつけることで、その型のラッパーとみなされます。
その他のフィールドは Default::default
によって初期化できる必要があります。
#[derive(DerivingVia)]
#[deriving(From, Display)]
pub struct Test<T>(#[underlying] i32, std::marker::PhantomData<T>);
Deriving via な使い方
以下のコードを見てください。
A
、B
ともに Display
を derive しておりません。
しかし、C
は Display
を derive できており、その挙動は i32
と同じです。
なんかすごそうですね。
use deriving_via::DerivingVia;
#[derive(DerivingVia)]
pub struct A(i32);
#[derive(DerivingVia)]
pub struct B(A);
#[derive(DerivingVia)]
#[deriving(Display(via: i32))]
pub struct C(B);
fn main() {
let c = C(B(A(42)));
println!("{c}"); // 42
}
よくみると、#[derive(DerivingVia)]
が A
と B
にもついています。
悲しいことです。
これは、Deriving via のしくみの影響です。
Deriving via のしくみ
Deref
の推移的な変換を利用しています。
そのため、#[derive(DerivingVia)]
は強制的に Deref
トレイトを impl しようとします。
#[derive(DerivingVia)]
が A
と B
にもついているのはそのためです。
#[derive(DerivingVia)]
#[deriving(Display(via: i32))]
pub struct C(B);
これはつぎのようなコードを生成します。
impl ::core::fmt::Display for C
{
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
let de: &i32 = self;
write!(f, "{}", de)
}
}
let de: &i32 = self;
で推移的な Type Coercion が起こります。
必要なだけ deref
が呼ばれ、self
を &i32
に変換するのです。
#[transitive]
DervingVia
は当然 Display
以外も derive することができます。
例えば、Add
を derive することができます。
しかし、ここで問題があります。
deref
を使って C
を i32
にすることはできますが、足し算を行ったあとに C
に戻す方法がマクロからはわからないのです。
マクロに戻し方を教えてあげる必要があります。
推移的な型変換をマクロに教える #[transitive]
アトリビュートを使います。
各型変換には Into
トレイトが要求されるので #[deriving(From)]
が必要になります。
use std::fmt::Display;
use deriving_via::DerivingVia;
#[derive(DerivingVia)]
#[deriving(From)]
pub struct A(i32);
#[derive(DerivingVia)]
#[deriving(From)]
pub struct B(A);
#[derive(DerivingVia)]
#[deriving(From, Add(via: i32), Display(via: i32))]
#[transitive(i32 -> A -> B -> C)]
pub struct C(B);
fn main() {
let c = C(B(A(42))) + C(B(A(42)));
println!("{c}");
}
実際どう使う?
Value Object に対して Serialize/Deserialize を derive する例。
use deriving_via::DerivingVia;
use serde::{Deserialize, Serialize};
use serde_json::json;
#[derive(Debug, DerivingVia)]
#[deriving(Display, From, Serialize(via: u32), Deserialize(via: u32))]
struct Id<T>(#[underlying] u32, std::marker::PhantomData<T>);
#[derive(Debug, DerivingVia)]
#[deriving(Display, From, FromStr, Serialize(via: String), Deserialize(via: String))]
struct Name<T>(#[underlying] String, std::marker::PhantomData<T>);
#[derive(Debug, Serialize, Deserialize)]
struct User {
id: Id<User>,
name: Name<User>,
}
fn main() {
let user = User {
id: 1u32.into(),
name: "mitama".parse().unwrap(),
};
let json = json!({ "user": user });
println!("{json}");
}
Discussion