tag:crieit.net,2005:https://crieit.net/tags/Serde/feed 「Serde」の記事 - Crieit Crieitでタグ「Serde」に投稿された最近の記事 2020-05-08T23:54:31+09:00 https://crieit.net/tags/Serde/feed tag:crieit.net,2005:PublicArticle/15890 2020-05-08T23:54:31+09:00 2020-05-08T23:54:31+09:00 https://crieit.net/posts/Serde-1-Derive Serde入門 (1) Derive属性の裏側をちょっと覗く <h1 id="Serde入門"><a href="#Serde%E5%85%A5%E9%96%80">Serde入門</a></h1> <h2 id="趣旨"><a href="#%E8%B6%A3%E6%97%A8">趣旨</a></h2> <p>Bayardという全文検索エンジンがあり保存されているドキュメントはJSON形式で取得できる. serde_jsonを使えば良いのだが, serde_jsonではhierarchical_facetというデータが上手く変換できない.</p> <pre><code class="rust">use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] enum Episode { Jo, Ha, Q, } #[derive(Debug, Serialize, Deserialize)] struct Human { id: String, name: String, appears_in: Vec<Episode>, } fn main() { let ikari_shinji = Human { id: "1".to_owned(), name: "Ikari Shinji".to_owned(), appears_in: vec![Episode::Jo, Episode::Ha, Episode::Q], }; let ikari_shinji_str = serde_json::to_string(&ikari_shinji).expect("cant't convert into string"); println!("{}", ikari_shinji_str); // {"id":"1","name":"Ikari Shinji","appears_in":["Jo","Ha","Q"]} let ayanami_rei_str = " { \"id\": \"2\", \"name\": \"Ayanami Rei\", \"appears_in\": [\"/episode/Jo\", \"/episode/Ha\", \"/episode/Q\"] }"; let anayami_rei: Human = serde_json::from_str(&ayanami_rei_str).unwrap(); println!("{:?}", anayami_rei); } </code></pre> <p>のayanami_rei_strをdeserializeしたい. 実行するとパニックが起きる.</p> <pre><code class="bash">thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error("unknown variant `/episode/Jo`, expected one of `Jo`, `Ha`, `Q`", line: 5, column: 36)', src/main.rs:34:30 </code></pre> <p>実はすごく簡単にこのエラーを消せるのですが, 今回はとりあえずserdeを概観し, Derive属性の裏側を覗き見てみましょう.</p> <h2 id="Serdeとは?"><a href="#Serde%E3%81%A8%E3%81%AF%3F">Serdeとは?</a></h2> <blockquote> <p>Serde is a framework for <strong>ser</strong>ializing and <strong>de</strong>serializing Rust data structures efficiently and generically.</p> </blockquote> <p><a target="_blank" rel="nofollow noopener" href="https://docs.serde.rs/serde/">Serde - Docs.rs</a></p> <h3 id="シリアライズとは?"><a href="#%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3%82%BA%E3%81%A8%E3%81%AF%3F">シリアライズとは?</a></h3> <p>直列化とか訳されたりします. 一般にデータの直列化とはバイト列や文字列への変換を指します. 通常Rustの構造体などはメモリ上に散らばって存在し, これらを参照することで一つの実体のように見せています. 対してバイト列や文字列はメモリ上に直線的な列を作って保存されています. また文字列にしておけばファイルに保存することができます. デシリアライズはその逆に直列化したデータを復元する作業です. こうすることで特定のデータをRustで読み込んで操作できるようになります.</p> <p>ここではRustのデータ構造を別のより一般的なデータ形式に変換する操作をシリアライズと定義することにします. デシリアライズはその逆です.</p> <h3 id="登場人物"><a href="#%E7%99%BB%E5%A0%B4%E4%BA%BA%E7%89%A9">登場人物</a></h3> <ul> <li>Rustデータ構造 (以下データ構造)</li> <li>Serdeデータ・モデル (以下データ・モデル)</li> <li>データ形式</li> </ul> <p>データ構造をデータ形式へと変換するのがSerdeの目的でした. 両者を仲介するAPIをデータ・モデルと呼んでいます.</p> <blockquote> <p>The Serde data model is the API by which data structures and data formats interact.</p> </blockquote> <p>つまりSerdeは二段階の処理を踏むわけです.</p> <p>データ構造 ↔️ データ・モデル ↔️ データ形式</p> <p>こんな感じで説明されるわけですがよく分かりませんよね.</p> <h3 id="データ形式"><a href="#%E3%83%87%E3%83%BC%E3%82%BF%E5%BD%A2%E5%BC%8F">データ形式</a></h3> <p>現在対応しているデータ形式は<a target="_blank" rel="nofollow noopener" href="https://docs.serde.rs/serde/#data-formats">Data formats -Docs.rs</a>を参照してください.</p> <h2 id="Serdeの基本"><a href="#Serde%E3%81%AE%E5%9F%BA%E6%9C%AC">Serdeの基本</a></h2> <h3 id="Derive属性"><a href="#Derive%E5%B1%9E%E6%80%A7">Derive属性</a></h3> <p>基本的な使い方は簡単です. Derive属性の引数にトレイト指定することでSerialize/Deserializeトレイトをコンパイル時に実装してくれます.</p> <pre><code class="rust">use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize, Debug)] struct Point { x: i32, y: i32, } fn main() { let point = Point { x: 1, y: 2 }; let serialized = serde_json::to_string(&point).unwrap(); println!("serialized = {}", serialized); let deserialized: Point = serde_json::from_str(&serialized).unwrap(); println!("deserialized = {:?}", deserialized); } </code></pre> <p>この際Cargo.tomlでは,</p> <pre><code class="toml">[dependencies] serde = { version = "1.0", features = ["derive"] } </code></pre> <p>のようにserdeの追加とfeaturesでderiveを指定しておきます.</p> <h3 id="Serialize/Deserializeトレイト"><a href="#Serialize%2FDeserialize%E3%83%88%E3%83%AC%E3%82%A4%E3%83%88">Serialize/Deserializeトレイト</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://serde.rs/derive.html">Using Derive - Serde</a></p> <p>Serialize/Deserializeトレイトで実装すべき振る舞いはserializeとdeserializeだけです.</p> <pre><code class="rust">pub trait Serialize { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer; } pub trait Deserialize<'de>: Sized { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de>; } </code></pre> <p>SerializeとDeserializeでは微妙にシグニチャが違いますが, 引数にSerializer/Deserializerという如何にもな名前の型を取ります.</p> <p>どうやら具体的な変換方法はこれらに委譲されるようです. また具体的な変換処理はデータ形式で違ってきますので, Serializer/Deserializerトレイトを実装した型が必要になるわけです.</p> <p>プリミティブ型には全てデフォルトのSerialize/Deserialize実装が存在します.</p> <blockquote> <p>Serde provides such impls for all of Rust's primitive types so you are not responsible for implementing them yourself, ...</p> </blockquote> <p><a target="_blank" rel="nofollow noopener" href="https://serde.rs/impl-serialize.html#serializing-a-primitive">Serializing a primitive - Serde</a><br /> <a target="_blank" rel="nofollow noopener" href="https://docs.serde.rs/serde/ser/trait.Serialize.html?search=#foreign-impls">Implementations on Foreign Types - Serialize</a><br /> <a target="_blank" rel="nofollow noopener" href="https://docs.serde.rs/serde/de/trait.Deserialize.html#foreign-impls">Implementations on Foreign Types - Deserialize</a></p> <p>例えばstr型に対しては以下のようなトレイト実装が与えられます.</p> <pre><code class="rust">impl Serialize for str { #[inline] fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { serializer.serialize_str(self) } } </code></pre> <p>要するに以下のようなコードがあった時に,</p> <pre><code class="rust">#[derive(Serialize)] struct Human { name: String, age: u8, appears_in: Vec<String>, } </code></pre> <p>次のように展開されることになります.</p> <pre><code class="rust">use serde::ser::{Serialize, SerializeStruct, Serializer}; struct Human { name: String, age: u8, appears_in: Vec<String>, } // This is what #[derive(Serialize)] would generate. impl Serialize for Human { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { let mut s = serializer.serialize_struct("Person", 3)?; s.serialize_field("name", &self.name)?; s.serialize_field("age", &self.age)?; s.serialize_field("phones", &self.appears_in)?; s.end() } } </code></pre> <p>これを実際にシリアライズすること考えてみましょう. Human構造体はSerializeトレイトを実装しているのでserializeメソッドが自動導出されています.</p> <pre><code class="rust">fn main() { let JO = String::from("Jo"); let HA = String::from("Ha"); let Q = String::from("Q"); let ikari_shinji = Human { name: "Ikari Shinji".to_owned(), age: 14, appears_in: vec![JO, HA, Q], }; // ikari_shinji.serialize(??); } </code></pre> <p>これでシリアライズはできるようになりましたが, 具体的な方法はserializerに委ねられます. この時点ではどのようなフォーマットで出力するのか定まっていないので当然と言えば当然ですが, 適切なSerializer型を定義することでikari_shinjiインスタンスをシリアライズできます.</p> <p><a target="_blank" rel="nofollow noopener" href="https://docs.serde.rs/serde/ser/trait.Serialize.html?search=#required-methods">Required methods - serde::ser::Serialize</a></p> <h3 id="Serializerトレイト"><a href="#Serializer%E3%83%88%E3%83%AC%E3%82%A4%E3%83%88">Serializerトレイト</a></h3> <p>serializerはシリアライズ用のデータ・モデルでデータのシリアライズ用のAPIを提供します. 型に応じて<a target="_blank" rel="nofollow noopener" href="https://docs.serde.rs/serde/trait.Serializer.html#required-methods">メソッド</a>が宣言されています. 例えば構造体なら<a target="_blank" rel="nofollow noopener" href="https://docs.serde.rs/serde/ser/trait.Serialize.html#tymethod.serialize">serialize_struct</a>というメソッドをシリアライズの時に呼び出しています.</p> <pre><code class="rust">use serde::ser::{Serialize, SerializeStruct, Serializer}; struct Person { name: String, age: u8, phones: Vec<String>, } // This is what #[derive(Serialize)] would generate. impl Serialize for Person { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { let mut s: SerializeStruct = serializer.serialize_struct("Person", 3)?; s.serialize_field("name", &self.name)?; s.serialize_field("age", &self.age)?; s.serialize_field("phones", &self.phones)?; s.end() } } </code></pre> <p>名前はSerde型の前に慣例的にserialize_というプレフィックスがつけられます.</p> <h3 id="Deserializerトレイト/Visitorトレイト"><a href="#Deserializer%E3%83%88%E3%83%AC%E3%82%A4%E3%83%88%2FVisitor%E3%83%88%E3%83%AC%E3%82%A4%E3%83%88">Deserializerトレイト/Visitorトレイト</a></h3> <p>DeserializeプロセスはSerializeとは少し違います. 単純にいろんな入力が想定される点が違います. そのためDeserializerは更に処理をVisitorに委譲します.</p> <p><a target="_blank" rel="nofollow noopener" href="https://docs.serde.rs/serde/de/trait.Visitor.html">serde::de::Visitor</a></p> <p>説明が難しいので具体例としてTomlの場合は見てみましょう. <a target="_blank" rel="nofollow noopener" href="https://docs.rs/toml/0.5.6/toml/de/fn.from_str.html">toml::de::from_str</a>には以下のような例が載っています.</p> <pre><code class="rust">use serde_derive::Deserialize; #[derive(Deserialize)] struct Config { title: String, owner: Owner, } #[derive(Deserialize)] struct Owner { name: String, } fn main() { let config: Config = toml::from_str(r#" title = 'TOML Example' [owner] name = 'Lisa' "#).unwrap(); assert_eq!(config.title, "TOML Example"); assert_eq!(config.owner.name, "Lisa"); } </code></pre> <p>from_strはこの入力文字列をDeserializerに渡して, deserialize関数内部で処理を実行します.</p> <pre><code class="rust">pub fn from_str<'de, T>(s: &'de str) -> Result<T, Error> where T: de::Deserialize<'de>, { let mut d = Deserializer::new(s); let ret = T::deserialize(&mut d)?; d.end()?; Ok(ret) } </code></pre> <p>さて元の例をcargo-expandで展開してみましょう. いろいろコードが増えますが, 実際の呼び出し場所は以下のようになります.</p> <pre><code class="rust">_serde::Deserializer::deserialize_struct( __deserializer, "Config", FIELDS, __Visitor { marker: _serde::export::PhantomData::<Config>, lifetime: _serde::export::PhantomData, }, ) </code></pre> <p>Config構造体に対応して<a target="_blank" rel="nofollow noopener" href="https://github.com/alexcrichton/toml-rs/blob/master/src/de.rs#L290">deserialize_struct</a>が呼び出されています. __Visitorというマクロ展開によって生成されたインスタンスも渡されています. このインスタンスが実際の処理担うわけです. ここからが実際には大変なところで, 興味がある人は処理を追ってみてください.</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>だいぶ内容がSerializerに偏ってしまいました. Serializeは比較的分かりやすいです. 例えばRustのコードをJSON化する場合どのような対応関係なのかはある程度予想がつきます. 少なくともRust側データ構造は決まっているからです. しかしDeserializeは比較的自由なJSONを型のきっちり決まったRustのデータ構造に仕立て直す必要があり, 自然と複雑化します. 例外はserde_jsonでシリアライズした者を復元する場合だけです.</p> <p>いずれにしても特殊なデータ形式に対応したいという開発者以外が独自にSerializeとかDeserializeは不要です. 多くの場合は属性を付与することでSerialize/Deserializeの挙動をカスタマイズできます. 次はそれを</p> <h2 id="課題"><a href="#%E8%AA%B2%E9%A1%8C">課題</a></h2> <p>BayardのJSONのデータをDeserializeする</p> <p>[Need help with #<a target="_blank" rel="nofollow noopener" href="https://users.rust-lang.org/t/need-help-with-serde-deserialize-with/18374">serde(deserialize_with)</a></p> <h2 id="補足"><a href="#%E8%A3%9C%E8%B6%B3">補足</a></h2> <p>補足では本論とは関係ないけど気になったことをメモしておきます. 良かったら読んでみてください.</p> <h3 id="Serdeの設計とSOLID原則"><a href="#Serde%E3%81%AE%E8%A8%AD%E8%A8%88%E3%81%A8SOLID%E5%8E%9F%E5%89%87">Serdeの設計とSOLID原則</a></h3> <p>SOLID原則とは,</p> <ul> <li>Single Responsibility Principle (SRP)</li> <li>Open Closed Principle (OCP)</li> <li>Liskov Substitution Principle (LSP)</li> <li>Interface Segregation Principle (ISP)</li> <li>Dependency Inversion Principle (DIP)</li> </ul> <p>の5つの原則の頭文字をとったものです.</p> <p>LSPにつてはRustには継承を言語レベルで採用していません. ただこの原則が型の互換性(多態性)に関する原則だと見るとトレイト境界で強制できます. serializeメソッドはトレイト境界でSerializerトレイトを実装したSerializer型を引数に取るという条件を課すことでLSPを満たしているとも言えます. そもそも継承だけでインターフェースとかがない頃にメソッドのシグニチャを揃えようというような話だったのかなと想像します. 多態性を実現する手段が継承しかない場合はこのような原則で縛りを設ける必要はありそうです. Rustの場合</p> <blockquote> <p>(あらゆる型の)スーパークラスであるTと境界を設定したサブクラスは代替可能である</p> </blockquote> <p>と表現すればそのまま当てはまりそうです.</p> <p>さてserializeメソッドはシリアライズの実行だけを担い, データの変換はSerializer型が提供するAPIが担いました. 一見すると分けすぎな気もしますが, 様々なデータ形式に対応する時にserialzierを別にしておくことで拡張性が保たれることが分かります. 社会でも指示を出す人と実行する人の役割は違うようにserializeの指示を出すのと実際のデータの変換という役割を適切に分離していると考えることもできます.</p> <p>ここでSerializerは依存性として外側から引数として渡されるような設計になっています. いわゆる依存性注入(DI)です. すでに述べましたがこの依存性はSerializerトレイトを実装している型であれば互換性があります. そもそもSerialize自体もトレイトであり抽象に依存する良い設計と言えそうです(DIP). Serde可能なデータ形式を追加したい開発者はこの関係に従って(縛られて?)ひたすら実装を行うだけです.</p> <p>逆にISPは満たしていないように見えます. <a target="_blank" rel="nofollow noopener" href="https://serde.rs/impl-serializer.html#implementing-a-serializer">Implementing a Serializer</a>にも</p> <blockquote> <p>The Serializer trait has a lot of methods ...</p> </blockquote> <p>とあります. トレイトの粒度としては荒いと言えます. これは不要なメソッドの実装も取り敢えずしなくてはいけないことになります. <del>ser.rsの実装を見ると萎えます</del> ではカスタマイズが必要な場合独自のSerializer型を実装する必要があるのかというと<a target="_blank" rel="nofollow noopener" href="https://serde.rs/attributes.html">serde属性</a>を使うことで特定の要素だけをカスタマイズできるようになっています. 例えばデシリアライズでデフォルト値を設定したい場合は</p> <pre><code class="rust">#[serde(default)] </code></pre> <p>を付与することで可能です.</p> <p>最後はOCPです. この原則は少し分かりにくいですが以下のように表現されます.</p> <blockquote> <p>the open/closed principle states "software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification"</p> </blockquote> <p><a target="_blank" rel="nofollow noopener" href="https://en.wikipedia.org/wiki/Open–closed_principle">Open–closed principle - Wikipedia</a></p> <p>(機能の追加自体がmodificationじゃないのかという感じはしますが)要するに他のコードへの変更なしに新しい機能を追加できるかということです. <a target="_blank" rel="nofollow noopener" href="https://serde.rs/impl-serializer.html">Implementing a Serializer</a>のser.rsを見てみましょう. データ構造, Serialize/Serializerトレイトがエンティティで機能の呼び出し側はto_stringという関数です.</p> <pre><code class="rust">pub fn to_string<T>(value: &T) -> Result<String> where T: Serialize, { let mut serializer = Serializer { output: String::new(), }; value.serialize(&mut serializer)?; Ok(serializer.output) } </code></pre> <p>OCPに適うということは, ある機能を拡張した場合に, このロジックに変更がなければ良いわけです. 今オレオレデータ形式がserde可能だとして考えましょう(oreクレートとでも呼びましょうか). ここでserializerに新しい型が対応したとしましょう. 例えばRustがxxx型に対応したとします(例えばf128型とか?あるいはクラスが追加されたとか?). 慣例に倣ってserialize_xxxが必要になりますが, to_stringのロジックに変更は必要ありません. OCPも満たしているようです.</p> <p>Sedeの面白いところは, Derive属性やserde属性を使うことでエンド・ユーザーはこうした実装を何も考慮しなくてもデータの変換が行えるようにしていることです. 反面黒魔術を多用しているためコードを読むのは難しいですが.</p> <h3 id="Required methodsとProvided methods"><a href="#Required+methods%E3%81%A8Provided+methods">Required methodsとProvided methods</a></h3> <p>Docs.rsにはこのような分類があるのですがRustでメソッドや関数に実装をオプションにするような構文があるという話は知りません. 私の勉強不足かとも思いましたが, 単純にデフォルトの実装の有無で見分けているようです.</p> <p><a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/questions/44642662/how-does-rust-know-which-trait-methods-are-required-or-provided">How does Rust know which trait methods are required or provided?</a></p> <p>実際に<a target="_blank" rel="nofollow noopener" href="https://github.com/serde-rs/serde/blob/33438850a6a8b0a3550619a60885cfc6f224e53f/serde/src/de/mod.rs#L1263">serde::de::Visitor</a>を見てみると<a target="_blank" rel="nofollow noopener" href="https://github.com/serde-rs/serde/blob/33438850a6a8b0a3550619a60885cfc6f224e53f/serde/src/de/mod.rs#L1289">expectingメソッド</a>はシグニチャの宣言だけです.</p> <pre><code class="rust">fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result; </code></pre> <p>一方<a target="_blank" rel="nofollow noopener" href="https://github.com/serde-rs/serde/blob/33438850a6a8b0a3550619a60885cfc6f224e53f/serde/src/de/mod.rs#L1294">visit_boolメソッド</a>はエラーを返すような仕様になっています.</p> <pre><code class="rust">fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E> whereE: Error, { Err(Error::invalid_type(Unexpected::Bool(v), &self)) } </code></pre> <h3 id="matchガード"><a href="#match%E3%82%AC%E3%83%BC%E3%83%89">matchガード</a></h3> <p>Derive属性で自動導出した場合どのようなSerializeトレイトが実装されるのでしょうか.</p> <pre><code class="rust">use serde::{Deserialize, Serialize}; #[derive(Serialize)] struct Human { name: String, age: u8, appears_in: Vec<String>, } fn main() { println!("cargo expand") } </code></pre> <p>これを<a target="_blank" rel="nofollow noopener" href="https://github.com/dtolnay/cargo-expand">cargo-expand</a>で展開すると以下のようなSerializeトレイトが実装されます.</p> <pre><code class="rust">let mut __serde_state = match _serde::Serializer::serialize_struct( __serializer, "Human", false as usize + 1 + 1 + 1, ) { _serde::export::Ok(__val) => __val, _serde::export::Err(__err) => { return _serde::export::Err(__err); } }; </code></pre> <p>適切なSerializerメソッドが呼び出されているのが分かります. <a target="_blank" rel="nofollow noopener" href="https://doc.rust-lang.org/reference/expressions/match-expr.html#match-expressions">Match式</a>は式を後置できるのでmathから波括弧までの間が式で, その戻り値でOkかErrかで判断されます. こういうのは<a target="_blank" rel="nofollow noopener" href="https://doc.rust-lang.org/reference/expressions/match-expr.html">Match guards</a>というらしいです. 何らかの形でMatch式が使われていることは予想していたのですが実際のコードを見てみると面白いですね.</p> <h3 id="triマクロ"><a href="#tri%E3%83%9E%E3%82%AF%E3%83%AD">triマクロ</a></h3> <p>例でserde_jsonではなくtomlを使ったのはsede_jsonはserialize/deserializeを呼び出さないことと, triマクロを使っているので説明に適さないと思ったからです.</p> <pre><code class="rust">macro_rules! tri { ($e:expr) => { match $e { crate::lib::Result::Ok(val) => val, crate::lib::Result::Err(err) => return crate::lib::Result::Err(err), } }; } </code></pre> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/serde-rs/json/blob/9354bec7ddf0733bae7666e64f0078d9d5f029d9/src/lib.rs#L419">lib.rs - serde-rs /json</a></p> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://serde.rs/">The Serde Book</a><br /> <a target="_blank" rel="nofollow noopener" href="https://docs.serde.rs/serde/index.html">serde - Docs.rs</a><br /> <a target="_blank" rel="nofollow noopener" href="https://docs.rs/toml/0.5.6/toml/index.html">toml - Docs.rs</a></p> ブレイン