Serde는 Rust의 데이터 직렬화/역직렬화를 담당하는 프레임워크로, Rust 생태계의 중추적인 위치를 차지하고 있다. 이번 글에서는 Serde의 인터페이스가 어떻게 정의되었는지 정리해보고자 한다.

Serde data model

Types

Serde는 Rust의 타입 시스템을 단순화하여 자체적인 데이터 모델을 정의한다.

  • 14종의 기본 자료형: bool, {i, u} x {8, 16, 32, 64, 128}, f32, f64, char
  • 문자열과 바이트열. serde의 문자열은 0-byte를 허용한다. 이 둘은 transient/borrowed/owned의 세 가지 형태로 사용될 수 있는데 이는 후술한다.
  • option: None 혹은 Some(T).
  • unit: ().
  • unit_struct: struct Unit 혹은 PhantomData<T> 등.
  • unit_variant: 필드 없는 enum variant.
  • newtype_struct: struct Millimeters(u8) 등.
  • newtype_variant: enum Enum { Variant(u8 }에서 Enum::Variant.
  • seq: 가변 개수의, 서로 타입 다를 수 있는, 여러 값들의 순서 있는 집합. ex) Vec<T>HashSet<T>1
  • tuple: 정적 개수의, 서로 타입 다를 수 있는, 여러 값들의 순서 있는 집합. 역직렬화 시점에 데이터를 읽지 않고도 개수를 미리 알 수 있다는 차이점이 있다. ex) (u8,), (String, u64, Vec<T>), [u64; 10]
  • tuple_struct: 튜플 구조체. ex) struct Rgb(u8, u8, u8)
  • tuple_variant: 튜플 variant. ex) enum Enum { Variant(u8, u8) }에서 Enum::Variant
  • map: 가변 개수의, 서로 타입 다를 수 있는 키-값 쌍들의 집합. ex) BTreeMap<K, V>. 가변 개수이므로 직렬화 시점에는 전체 데이터를 다 읽기 전까지 개수를 모를 수도 있고, 역직렬화 시점에도 직렬화된 데이터를 끝까지 읽어야 알 수 있다.
  • struct: 정적 개수의, 서로 타입 다를 수 있는 키-값 쌍들의 집합. 키들은 컴파일 시점에 고정된 문자열이며 데이터를 모두 읽지 않고도 알 수 있다 (&'static str').
  • struct_variant: 열거형의 struct variant. ex) enum Enum { Variant { x: u8 } }Enum::Variant

Serde data model은 Rust의 것과 유사하기 때문에 비슷한 강력함을 자랑한다. 예를 들어 std::ffi::OsString은 플랫폼마다 표현 방식이 다르므로(Unix는 Vec<u8>, Windows는 Vec<u16>) 어느 한 쪽으로 가정해서 저장하면 손실이나 정의되지 않은 값이 발생할 수밖에 없는데, 각 플랫폼을 newtype variant로 하는 enum을 정의하면 손쉽게 문제가 해결된다.

Serialize & Serializer

Rust의 데이터 구조를 직렬화할 때는 SerializeSerializer 트레잇이 관여한다. SerializeRust의 데이터 구조(struct, enum, …)에 의해 구현되어서, 자신을 Serde의 데이터 모델로 변환하는 방법을 정의한다. 이 때, 정의된 변환 방법에 따라 SerializeSerializer 트레잇의 메서드를 순서대로 호출한다.

Serializer데이터 포맷에 대해 구현되어서, Serialize에 의해 호출될 때마다 Serde 데이터 모델로부터 적절히 직렬화된 데이터를 생성하게 된다.

Rust data --(Serialize)-> Serde data model --(Serilaizer)-> Serialized data

정의를 보면 더 명확해진다. Serialize 트레잇에는 단 하나의 메서드만 존재한다. 직렬화를 위한 일종의 진입 지점이 되는 셈이다.

pub trait Serialize {
    // Required method
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
       where S: Serializer;
}

Serialize::serialize는 Serde 데이터 모델에 대응되는 메서드를 모두 가지고 있다. serialize_{compound}류의 경우에는 한 번에 모든 데이터를 받을 수 없기 때문에(Rust에는 variadic이 없다), 각 컴파운드 타입마다 별도의 상태 기계를 반환하여 해당 컴파운드 타입의 원소들을 밀어넣을 수 있게 하고 있다.

pub trait Serializer: Sized {
    type Ok;
    type Error: Error;
    type SerializeSeq: SerializeSeq<Ok = Self::Ok, Error = Self::Error>;
    type SerializeTuple: SerializeTuple<Ok = Self::Ok, Error = Self::Error>;
    type SerializeTupleStruct: SerializeTupleStruct<Ok = Self::Ok, Error = Self::Error>;
    type SerializeTupleVariant: SerializeTupleVariant<Ok = Self::Ok, Error = Self::Error>;
    type SerializeMap: SerializeMap<Ok = Self::Ok, Error = Self::Error>;
    type SerializeStruct: SerializeStruct<Ok = Self::Ok, Error = Self::Error>;
    type SerializeStructVariant: SerializeStructVariant<Ok = Self::Ok, Error = Self::Error>;

    // Required methods
    fn serialize_bool(self, v: bool) -> Result<Self::Ok, Self::Error>;
    fn serialize_i8(self, v: i8) -> Result<Self::Ok, Self::Error>;
    fn serialize_i16(self, v: i16) -> Result<Self::Ok, Self::Error>;
    // ...
    fn serialize_some<T>(self, value: &T) -> Result<Self::Ok, Self::Error>
       where T: Serialize + ?Sized;
    fn serialize_unit(self) -> Result<Self::Ok, Self::Error>;
    fn serialize_unit_struct(
        self,
        name: &'static str
    ) -> Result<Self::Ok, Self::Error>;
    // ...
       where T: Serialize + ?Sized;
    fn serialize_seq(
        self,
        len: Option<usize>
    ) -> Result<Self::SerializeSeq, Self::Error>;
    fn serialize_tuple(
        self,
        len: usize
    ) -> Result<Self::SerializeTuple, Self::Error>;
    // ...
}

Deserialize & Deserializer & Visitor

역직렬화의 경우 이 세 트레잇이 관여한다. Deserialize는 Rust의 데이터 구조에 대해 구현되어서, Serde의 데이터 모델에서 어떻게 Rust 데이터 구조로 변환될 수 있는지를 정의한다. 이 정의는 Deserializer 트레잇의 메서드를 호출하여 Visitor(를 구현하는 구조체)를 전달하는 식으로 이루어진다.

Deserializer 트레잇은 데이터 포맷에 대해 구현되어서, 자신이 ‘들고 있는’ 데이터의 일부를 해석할 때마다 전달받은 Visitor의 메서드를 호출한다. Visitor에는 Serde 데이터 모델에 대응되는 visit_* 메서드가 있어서, Serde의 데이터 모델에 해당하는 값이 어떻게 Rust 데이터 구조로 변환될 수 있는지를 정의한다.

예시를 들어보자. 기본 자료형에 대한 역직렬화 구현은 다음과 같이 한다. (구현하지 않은 visit_* 메서드는 모두 Error::invalid_type을 반환하도록 기본 구현이 주어져 있다.)

struct BoolVisitor;

impl<'de> Visitor<'de> for BoolVisitor {
    type Value = bool;

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

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

impl<'de> Deserialize<'de> for bool {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_bool(BoolVisitor)
    }
}

Vec<T>와 같은 composite type에 대한 역직렬화 구현은 다음과 같다.

impl<'de, T> Deserialize<'de> for Vec<T>
where
    T: Deserialize<'de>,
{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct VecVisitor<T> {
            marker: PhantomData<T>,
        }

        impl<'de, T> Visitor<'de> for VecVisitor<T>
        where
            T: Deserialize<'de>,
        {
            type Value = Vec<T>;

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

            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
            where
                A: SeqAccess<'de>,
            {
                let mut values = Vec::with_capacity(size_hint::cautious(seq.size_hint()));

                while let Some(value) = try!(seq.next_element()) {
                    values.push(value);
                }

                Ok(values)
            }
        }

        let visitor = VecVisitor {
            marker: PhantomData,
        };
        deserializer.deserialize_seq(visitor)
    }
}

마지막으로, 다음 구조체에 #[derive(Deserialize)]를 걸고 macro expansion을 보면 아래와 같다. (가독성을 위해 단순화하였음)

struct Foo {
    bar: u32,
    baz: Option<(i64, f64)>,
    qux: HashMap<String, u8>,
}
코드가 너무 길어서 접었습니다.
impl<'de> Deserialize<'de> for Foo {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        enum Field {
            field0,
            field1,
            field2,
            ignore,
        }
        struct FieldVisitor;
        impl<'de> de::Visitor<'de> for FieldVisitor {
            type Value = Field;
            fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
                Formatter::write_str(formatter, "field identifier")
            }
            fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
            where
                E: de::Error,
            {
                match value {
                    0u64 => Ok(Field::field0),
                    1u64 => Ok(Field::field1),
                    2u64 => Ok(Field::field2),
                    _ => Ok(Field::ignore),
                }
            }
            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
            where
                E: de::Error,
            {
                match value {
                    "bar" => Ok(Field::field0),
                    "baz" => Ok(Field::field1),
                    "qux" => Ok(Field::field2),
                    _ => Ok(Field::ignore),
                }
            }
            fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
            where
                E: de::Error,
            {
                match value {
                    b"bar" => Ok(Field::field0),
                    b"baz" => Ok(Field::field1),
                    b"qux" => Ok(Field::field2),
                    _ => Ok(Field::ignore),
                }
            }
        }
        impl<'de> Deserialize<'de> for Field {
            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
            where
                D: Deserializer<'de>,
            {
                Deserializer::deserialize_identifier(deserializer, FieldVisitor)
            }
        }
        struct Visitor<'de> {
            marker: PhantomData<Foo>,
            lifetime: PhantomData<&'de ()>,
        }
        impl<'de> de::Visitor<'de> for Visitor<'de> {
            type Value = Foo;
            fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
                Formatter::write_str(formatter, "struct Foo")
            }
            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
            where
                A: de::SeqAccess<'de>,
            {
                let field0 = match de::SeqAccess::next_element::<u32>(&mut seq)? {
                    Some(value) => value,
                    None => {
                        return Err(de::Error::invalid_length(
                            0usize,
                            &"struct Foo with 3 elements",
                        ));
                    }
                };
                let field1 = match de::SeqAccess::next_element::<Option<(i64, f64)>>(&mut seq)? {
                    Some(value) => value,
                    None => {
                        return Err(de::Error::invalid_length(
                            1usize,
                            &"struct Foo with 3 elements",
                        ));
                    }
                };
                let field2 = match de::SeqAccess::next_element::<HashMap<String, u8>>(&mut seq)? {
                    Some(value) => value,
                    None => {
                        return Err(de::Error::invalid_length(
                            2usize,
                            &"struct Foo with 3 elements",
                        ));
                    }
                };
                Ok(Foo {
                    bar: field0,
                    baz: field1,
                    qux: field2,
                })
            }
            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
            where
                A: de::MapAccess<'de>,
            {
                let mut field0: Option<u32> = None;
                let mut field1: Option<Option<(i64, f64)>> = None;
                let mut field2: Option<HashMap<String, u8>> = None;
                while let Some(key) = match de::MapAccess::next_key::<Field>(&mut map) {
                    Ok(val) => val,
                    Err(err) => {
                        return Err(err);
                    }
                } {
                    match key {
                        Field::field0 => {
                            if Option::is_some(&field0) {
                                return Err(<A::Error as de::Error>::duplicate_field("bar"));
                            }
                            field0 = Some(de::MapAccess::next_value::<u32>(&mut map)?);
                        }
                        Field::field1 => {
                            if Option::is_some(&field1) {
                                return Err(<A::Error as de::Error>::duplicate_field("baz"));
                            }
                            field1 =
                                Some(de::MapAccess::next_value::<Option<(i64, f64)>>(&mut map)?);
                        }
                        Field::field2 => {
                            if Option::is_some(&field2) {
                                return Err(<A::Error as de::Error>::duplicate_field("qux"));
                            }
                            field2 =
                                Some(de::MapAccess::next_value::<HashMap<String, u8>>(&mut map)?);
                        }
                        _ => {
                            let _ = de::MapAccess::next_value::<de::IgnoredAny>(&mut map)?;
                        }
                    }
                }
                let field0 = match field0 {
                    Some(field0) => field0,
                    None => de::missing_field("bar")?,
                };
                let field1 = match field1 {
                    Some(field1) => field1,
                    None => de::missing_field("baz")?,
                };
                let field2 = match field2 {
                    Some(field2) => field2,
                    None => de::missing_field("qux")?,
                };
                Ok(Foo {
                    bar: field0,
                    baz: field1,
                    qux: field2,
                })
            }
        }
        const FIELDS: &'static [&'static str] = &["bar", "baz", "qux"];
        Deserializer::deserialize_struct(
            deserializer,
            "Foo",
            FIELDS,
            Visitor {
                marker: PhantomData::<Foo>,
                lifetime: PhantomData,
            },
        )
    }
}

The 'de lifetime

Deserialize<'de>, Deserializer<'de>에 붙어 있는 'de 라이프타임은 역직렬화하기 위해 주어진 데이터의 라이프타임이다. 즉, 해당 데이터로부터 참조를 생성하여 zero-copy 역직렬화를 구현하기 위해서는 해당 라이프타임을 사용해야만 한다.

struct Struct<'a> {
    a: &'a str,
}
impl<'de> Deserialize<'de> for Struct<'de> {}

구조체가 어떠한 참조도 들고 있지 않은 경우에는 'de 라이프타임이 어떤 것이든 전혀 상관하지 않을 것이다. 이를 HRTB(higher-ranked trait bound)로 표현하면 for<'de> Deserialize<'de>가 될 것이고 Serde에서는 이를 축약하여 DeserializeOwned 라는 trait alias를 두고 있다.

References


  1. 작성자 주: HashSet 자체에는 순서가 있을 리 만무하나 serialize/deserialize하는 시점에 순서가 정해지므로(직렬화된 데이터는 항상 순서가 있으니) seq으로 모델링할 수 있다고 생각된다. ↩︎