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의 데이터 구조를 직렬화할 때는 Serialize
와 Serializer
트레잇이 관여한다. Serialize
는 Rust의 데이터 구조(struct, enum, …)에 의해 구현되어서, 자신을 Serde의 데이터 모델로 변환하는 방법을 정의한다. 이 때, 정의된 변환 방법에 따라 Serialize
는 Serializer
트레잇의 메서드를 순서대로 호출한다.
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
-
작성자 주: HashSet 자체에는 순서가 있을 리 만무하나 serialize/deserialize하는 시점에 순서가 정해지므로(직렬화된 데이터는 항상 순서가 있으니) seq으로 모델링할 수 있다고 생각된다. ↩︎