🏜️

serdeに入門してSerializerとDeserializerを実装した

2025/02/25に公開

自身のブログにも同じ内容の記事を投稿しています
https://www.tunamaguro.dev/articles/20250224-serdeに入門した/

serdeの入門として、serde::Serializerserde::Deserializerを実装したので、serdeについて理解したことを記録として残します

https://github.com/tunamaguro/messagepack-rs

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Data<'a> {
    compact: bool,
    schema: u8,
    less: &'a str,
}

let buf: &[u8] = &[
    0x83, 0xa7, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0xc3, 0xa6, 0x73, 0x63, 0x68,
    0x65, 0x6d, 0x61, 0x00, 0xa4, 0x6c, 0x65, 0x73, 0x73, 0xa9, 0x74, 0x68, 0x61, 0x6e,
    0x20, 0x6a, 0x73, 0x6f, 0x6e,
];

let data = messagepack_serde::from_slice::<Data<'_>>(buf).unwrap();
let expected = Data {
    compact: true,
    schema: 0,
    less: "than json",
};
assert_eq!(data, expected);

let mut deserialized = [0u8; 33];
let len = messagepack_serde::to_slice(&expected, &mut deserialized).unwrap();
assert_eq!(&deserialized[..len], buf);

既存実装のrmp-serdeより機能を削ぎ落している分少しだけ早いです(要検証)

no_std対応ですが実装はかなり雑なので、バグがある可能性が高いです

実装参考

作った理由

単純に勉強目的で作成しました。RustでWeb周りを触る際、serdeserde-jsonを使うことが多いと思いますが、なぜserde_json::from_str(data)でデータをパースできるのか分からなかったため「とりあえずやってみたら理解できるか...」という気持ちで始めました。

Serde data model

実装するうえでもっとも重要だと感じたのは、このSerde data modelです。SerializerDeserializerは任意のフォーマットから、Serde data modelに値を変換することのみを役割としています。つまりSerializerSerde data modelを何らかのフォーマットに変換するもの、Deserializerは何らかのフォーマットをパースしながら、Serde data modelへ値をマッピングしていくものです。図にするとこんな感じです

Serde data modelの気持ち

いったんSerde data modelを経由することで、変換先と変換元でN×M個の実装が必要になるのを、N+M個に抑えているのだと思います

Serializerの実装

Serializerの実装はここにあります

今回実装するフォーマットとしてMessagePackを選択しました。仕様が小さいこと、machine readableなこと、JSONに構造が近いためserde-jsonが実装参考になりそうなことが理由です。

Serializerトレイトで要求されている28個のメソッドを実装する形になります。i8strのようなprimitive typeはそのままなので難しくなく引数に流れてくる値を変換して書き込むだけです。serialize_someに関しては少し違い、Option<T>の中身、つまり何らかのSerializeが実装された値が渡されるので、そのメソッドをそのまま呼び出します。呼び出す先はserialize_i8などのメソッドですが、その辺はSerializeトレイトが決定するのでSerializerは知る必要はありません

    fn serialize_bool(self, v: bool) -> Result<Self::Ok, Self::Error> {
        self.current_length += v.encode_to_iter_mut(self.buf.by_ref())?;
        Ok(())
    }

    fn serialize_i8(self, v: i8) -> Result<Self::Ok, Self::Error> {
        self.current_length += v.encode_to_iter_mut(self.buf.by_ref())?;
        Ok(())
    }

    fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
        self.current_length += v.encode_to_iter_mut(self.buf.by_ref())?;
        Ok(())
    }

// snip

    fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok, Self::Error> {
        self.current_length += BinaryEncoder(v).encode_to_iter_mut(self.buf.by_ref())?;
        Ok(())
    }

    fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
        self.current_length += NilEncoder.encode_to_iter_mut(self.buf.by_ref())?;
        Ok(())
    }

    fn serialize_some<T>(self, value: &T) -> Result<Self::Ok, Self::Error>
    where
        T: ?Sized + ser::Serialize,
    {
        value.serialize(self)
    }

https://github.com/tunamaguro/messagepack-rs/blob/main/crates/messagepack-serde/src/ser/mod.rs#L55-L137

続いてseqtupleのシリアライズを実装します。Serde data modelと対応するRustの構造体は次のような形になります

// seq
let _ = Vec::<u8>::new();
// tuple
let _ = ("123",456,7.89);
// tuple_struct
struct S(bool, i32)

これらのシリアライズはSerializerではなく、SerializeSeqSerializeTupleStructという別のトレイトで行います。今回の実装では、これらはすべてMessagePackのArrayとしてシリアライズすることにしたため、同じ構造体にまとめて実装しました。各要素ごとにSerializeSeq::serialize_elementが順に呼び出されるようになっているみたいです。そのため今回はSerializer側でArrayのフォーマットだけ書き込み(長さごとに用いるフォーマットが違うため)、中身のシリアライズのみSerializeSeqで行っています。

// Serializer側(mod.rs)

    fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
        let len = len.ok_or(Error::SeqLenNone)?;
        self.current_length +=
            ArrayFormatEncoder::new(len).encode_to_iter_mut(self.buf.by_ref())?;
        Ok(seq::SerializeSeq::new(self))
    }

    fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, Self::Error> {
        self.current_length +=
            ArrayFormatEncoder::new(len).encode_to_iter_mut(self.buf.by_ref())?;
        Ok(seq::SerializeSeq::new(self))
    }

    fn serialize_tuple_struct(
        self,
        _name: &'static str,
        len: usize,
    ) -> Result<Self::SerializeTupleStruct, Self::Error> {
        self.current_length +=
            ArrayFormatEncoder::new(len).encode_to_iter_mut(self.buf.by_ref())?;
        Ok(seq::SerializeSeq::new(self))
    }

// SerializeSeq側(seq.rs)
impl<'a, 'b, Buf> SerializeSeq<'a, 'b, Buf> {
    pub(crate) fn new(ser: &'a mut Serializer<'b, Buf>) -> Self {
        Self { ser }
    }
}

impl<'b, Buf> ser::SerializeSeq for SerializeSeq<'_, 'b, Buf>
where
    Buf: Iterator<Item = &'b mut u8>,
{
    type Ok = ();
    type Error = Error;

    fn serialize_element<T>(&mut self, value: &T) -> Result<(), Self::Error>
    where
        T: ?Sized + ser::Serialize,
    {
        value.serialize(self.ser.as_mut())
    }

    fn end(self) -> Result<Self::Ok, Self::Error> {
        Ok(())
    }
}

impl<'b, Buf> ser::SerializeTuple for SerializeSeq<'_, 'b, Buf>
where
    Buf: Iterator<Item = &'b mut u8>,
{
    type Ok = ();
    type Error = Error;

    fn serialize_element<T>(&mut self, value: &T) -> Result<(), Self::Error>
    where
        T: ?Sized + ser::Serialize,
    {
        ser::SerializeSeq::serialize_element(self, value)
    }

    fn end(self) -> Result<Self::Ok, Self::Error> {
        ser::SerializeSeq::end(self)
    }
}

次にmap用の実装をします。mapHashMapのようなキーとバリューのペアを持つ構造体に対応します。こちらもseqと同様にSerializeMapというトレイトが用意されているので、そちらに実装します。serialize_keyserialize_valueが順に呼び出されるので、それを順にシリアライズするだけです。structも同様なので、詳細は実装を確認してください

// Serializer側(mod.rs)
    fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
        let len = len.ok_or(Error::SeqLenNone)?;
        self.current_length += MapFormatEncoder::new(len).encode_to_iter_mut(self.buf.by_ref())?;
        Ok(map::SerializeMap::new(self))
    }

// SerializeMap側(map.rs)
pub struct SerializeMap<'a, 'b, Buf> {
    ser: &'a mut Serializer<'b, Buf>,
}

impl<'a, 'b, Buf> SerializeMap<'a, 'b, Buf> {
    pub(crate) fn new(ser: &'a mut Serializer<'b, Buf>) -> Self {
        Self { ser }
    }
}

impl<'b, Buf> ser::SerializeMap for SerializeMap<'_, 'b, Buf>
where
    Buf: Iterator<Item = &'b mut u8>,
{
    type Ok = ();
    type Error = Error;

    fn serialize_key<T>(&mut self, key: &T) -> Result<(), Self::Error>
    where
        T: ?Sized + ser::Serialize,
    {
        key.serialize(self.ser.as_mut())
    }

    fn serialize_value<T>(&mut self, value: &T) -> Result<(), Self::Error>
    where
        T: ?Sized + ser::Serialize,
    {
        value.serialize(self.ser.as_mut())
    }

    fn end(self) -> Result<Self::Ok, Self::Error> {
        Ok(())
    }
}

残っているのはXXX_variant系のモノです。これらは4つありそれぞれenumの要素に対応しています

enum S{
    UnitVariant, // unit_variant
    NewTypeVariant(bool), // newtype_variant
    TupleVariant(u8,u8), // tuple_variant
    StructVariant{a:u8}, // struct_variant
}

今回はunit_variantを文字列、newtype_variantをその中身、tuple_variantおよびstruct_variantをその名前をキーにした構造体にマッピングします。以下はJSONでの変換イメージです

S::UnitVariant // -> "UnitVariant"
S::NewTypeVariant // -> true
S::TupleVariant(1,2) // -> {"TupleVariant":[1,2]}
S::StructVariant{a:3} // -> {"StructVariant":{"a":3}}

実装は以下のようになります。tuple_variantおよびstruct_variantseqmapと同様にそれ専用のトレイトがあるので、そちらの実装が必要です

    fn serialize_unit_variant(
        self,
        _name: &'static str,
        _variant_index: u32,
        variant: &'static str,
    ) -> Result<Self::Ok, Self::Error> {
        self.serialize_str(variant)
    }

// snip

    fn serialize_newtype_variant<T>(
        self,
        _name: &'static str,
        _variant_index: u32,
        variant: &'static str,
        value: &T,
    ) -> Result<Self::Ok, Self::Error>
    where
        T: ?Sized + ser::Serialize,
    {
        self.current_length += MapFormatEncoder::new(1).encode_to_iter_mut(self.buf.by_ref())?;
        self.serialize_str(variant)?;
        value.serialize(self.as_mut())
    }

// snip

    fn serialize_tuple_variant(
        self,
        _name: &'static str,
        _variant_index: u32,
        variant: &'static str,
        len: usize,
    ) -> Result<Self::SerializeTupleVariant, Self::Error> {
        self.current_length += MapFormatEncoder::new(1).encode_to_iter_mut(self.buf.by_ref())?;
        self.serialize_str(variant)?;
        self.current_length +=
            ArrayFormatEncoder::new(len).encode_to_iter_mut(self.buf.by_ref())?;
        Ok(seq::SerializeSeq::new(self))
    }

// snip

    fn serialize_struct_variant(
        self,
        name: &'static str,
        _variant_index: u32,
        variant: &'static str,
        len: usize,
    ) -> Result<Self::SerializeStructVariant, Self::Error> {
        self.current_length += MapFormatEncoder::new(1).encode_to_iter_mut(self.buf.by_ref())?;
        self.serialize_str(variant)?;
        self.serialize_struct(name, len)
    }

残っているSerde data modelunitnewtype_structなどを実装すればSerializerは完成です。これはテストを書いていて気付いたことですが、serdeのデフォルトでは&[u8]をシリアライズする際に、Serializer::serialize_bytesではなくSerializer::serialize_seqが呼ばれます。大きいバイト列などではこの速度差は8~9倍あり遅いため、バイト列を使う場合はserde-bytesを使いましょう。構造体であれば#[serde(with = "serde_bytes")]をつけるだけ、そのままの&[u8]であれば次のようにして使えます

    #[test]
    fn encode_bytes() {
        // default &[u8] not call serialize_bytes
        let v = serde_bytes::Bytes::new(&[5, 4, 3, 2, 1, 0]);

        let buf = &mut [0u8; 128];
        let len = to_slice(&v, buf).unwrap();
        assert_eq!(buf[..len], [0xc4, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00]);
    }

serde_bytesを使ったとき: 450us
serde_bytesを使わないとき: 3.2ms

Deserializerの実装

Deserializerの実装はここにあります

こちらはSerializerの逆でバイト列をSerde data modelに変換します。こちらもSerde data modelの数だけメソッドが必要です。今回はDeserializerを次のように定義しました。Deserializer周りで毎回出てくる謎のライフタイム'deは、入力のバイト列などの参照を指していたことがわかります

#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)]
pub struct Deserializer<'de> {
    input: &'de [u8],
}

実際にデシリアライズするコード例は次のような形です

impl<'de> Deserializer<'de> {
    pub fn from_slice(input: &'de [u8]) -> Self {
        Deserializer { input }
    }

    // 便利用の関数。Vのデコードを試して、成功すれば自身の参照先を進める
    fn decode<V: Decode<'de>>(&mut self) -> Result<V::Value, Error> {
        let (decoded, rest) = V::decode(self.input)?;
        self.input = rest;
        Ok(decoded)
    }
}

impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> {
    type Error = Error;

    fn deserialize_any<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
    where
        V: de::Visitor<'de>,
    {
        Err(Error::AnyIsUnsupported)
    }

    fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: de::Visitor<'de>,
    {
        let decoded = self.decode::<bool>()?;
        visitor.visit_bool(decoded)
    }

    fn deserialize_i8<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: de::Visitor<'de>,
    {
        let decoded = self.decode::<i8>()?;
        visitor.visit_i8(decoded)
    }
// snip

大体の雰囲気は同じですが、見慣れないVisitorというものが存在しています。VisitorDeserializerが返す値を使い実際にその構造体を作成する役目を負っています。serdeがデフォルトで実装しているVisitorSerializeの例を見てみます。これを見るとDeserialize -> Deserializer -> Visitor -> Deserializeの順で呼び出されていることがわかります。

struct CharVisitor;

impl<'de> Visitor<'de> for CharVisitor {
    type Value = char;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("a character")
    }

    #[inline]
    fn visit_char<E>(self, v: char) -> Result<Self::Value, E>
    where
        E: Error,
    {
        Ok(v)
    }

    #[inline]
    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: Error,
    {
        let mut iter = v.chars();
        match (iter.next(), iter.next()) {
            (Some(c), None) => Ok(c),
            _ => Err(Error::invalid_value(Unexpected::Str(v), &self)),
        }
    }
}

impl<'de> Deserialize<'de> for char {
    #[inline]
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_char(CharVisitor)
    }
}

https://github.com/serde-rs/serde/blob/v1.0.218/serde/src/de/impls.rs#L548-L586

これはただのcharなのでまったく嬉しさがわかりませんが、次のtuple_structをデシリアライズする例を考えます。CharVisitorを組み合わせるだけで、新しい構造体も作ることができます

struct S(char, char);

impl <'de> Deserialize<'de> for S {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let ele1 = deserializer.deserialize_char(CharVisitor)?;
        let ele2 = deserializer.deserialize_char(CharVisitor)?;
        Ok(Self(ele1,ele2))
    }
}

またnewtype_structも対応したVisitorがあれば、それも流用して別の構造体にも使えることになります

struct NewType(bool);

struct NewTypeVisitor;

impl<'de> Visitor<'de> for NewTypeVisitor {
    type Value = NewType(bool);

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("a bool")
    }

    fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
    where
        E: Error,
    {
        Ok(NewType(v))
    }
}

struct K(NewType)

impl <'de> Deserialize<'de> for S {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let ele1 = deserializer.deserialize_bool(NewType)?;
        Ok(Self(ele1))
    }
}

長々と書きましたが、つまるところdeerialize_XXXで要求されている値のデシリアライズを試して、成功すればその値をVisitorの対応するメソッドに渡せば良いわけです。というわけでそれを地道に実装します。seqmapSerializerと同じように対応するトレイトde::SeqAccessなどを実装する形になります。つまり、同じような内容が長々と続くので詳細は割愛します。知りたい方は実装を読んでください

1つ嵌ったミスとして、&str&[u8]のような借用型でデシリアライズするとき、Visitor::visit_strではなくVisitor::visit_borrowed_strのようなvisit_borrowed_XXXを呼び出す必要があります。ドキュメントにも書いてありますがvisit_strはそのライフタイムより長く値を利用するために値をコピーするため、ゼロコピーである&strには使えません。今回はno_stdでメモリ割り当てが出来ないので、visit_borrowed_strを呼び出す必要があります。呼び出さないとエラーになります

👇&strのデシリアライズでvisit_strを呼び出していたため、しばらく嵌っていた際のエラーメッセージ


stderr:

thread 'main' panicked at crates/messagepack-serde/src/../README.md:20:59:
called `Result::unwrap()` on an `Err` value: Message("invalid type: string \"than json\", expected a borrowed string")
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

終わりに

とりあえず動くようにはなりましたが、機能的にはボロボロでほとんど何もできません。残りのやってみたいこととしては下のような感じです

  • std::io::Writeへの対応
  • extension周りの実装
  • よりデータが小さくなるようにシリアライズ、小さい値からキャストしてデシリアライズする
  • なんでも入るValueの実装

2日程度でできると思っていたのですが、結局5日かかりました。Serializer/Deserializerを実装することでserdeが何をやっているのか、少しだけ理解できるようになったのは良い点だと思っています。次はderiveのような手続き型マクロに挑戦してみたいです

Discussion