serde你在干什么 - serialize

serde 源码解析 | Serialization篇

关于serialize的过程,文章 understanding serde 讲得非常详细、非常好,用的例子还是真实的源码。推荐。

这篇文章最大的价值,是解释清楚了什么是所谓的serde data model

它本身并不是一种中间的数据结构,而是一种纯粹的概念上的分类。

通常,对于序列化和反序列化,一个自然的想法,是建立一个中间抽象层,把Rust中的数据结构映射成中间的抽象层,然后再基于这个统一的抽象层,比如一个大的enum,然后对enum中的项目,则可以采用不同的翻译方式,转化成json、yaml等格式。

有了这个想法,再看 SerializeSerializer trait的名字,望文生义都觉得没什么不妥。Serialize负责将Rust的数据结构转换到中间层,Serializer再负责把中间层翻译成具体的json、yaml。而中间层就是serde data model。这个模式其实很常见,连rust的编译都没逃过,想想Rustc、LLVM、MIR,是不是呼应上了?都呼应上了。

但是事情并不是这样的。

带着好奇,不妨来看代码。接下来用的,是在准备篇中介绍的简化版 serde_json。

故事开始

序列化的开始是在bin/inpsect.rs调用了serde_example::to_string

1
2
// inspect.rs
let ser_cmd = to_string(&cmd).unwrap();

to_string 构建了一个crate::ser::Serializer类型的实例serialzer,包含一个字段output,类型为String,可以猜到,接下来的序列化,就是serialzer的一场”看图作文”练习,根据内存中的数据,按照对应的格式,写到self.output中。

这个动作开始于value.serialize(&mut serializer)?;,魔法的开始之地,其中value是传入的Command实例。Commandserialze方法,由derive自动生成,所以接下来要看derive_ser.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ser.rs

pub struct Serializer {
output: String,
}

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)
}

从serde::Serialize的角度

之前我们猜,serde::Serialize负责将Rust的原生类型整合为一个中间类型。现在就可以看到实际发生的,和猜想的有什么不同。serialize方法一进,劈头盖脸就是一个match,结构一目了然,四个枚举项,就问你们谁要序列化?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const _IMPL_SERIALIZE_FOR_Command: () = {
#[allow(unknown_lints)]
#[allow(rust_2018_idioms)]
extern crate serde as _serde;
#[automatically_derived]
impl _serde::Serialize for Command {
fn serialize<__S>(
&self,
__serializer: __S,
) -> _serde::export::Result<__S::Ok, __S::Error>
where
__S: _serde::Serializer,
{
match *self {
Command::Set { ref key, ref value } => ...
Command::Get { ref key } => ...
Command::Rm(ref __field0) => ...
Command::Quit => ...
}
}
}
};

比如Command::Set需要序列化,主体结构也非常明晰,给需要写作文的serializer安排四个步骤:

  1. serialize_struct_variant,现在要序列化一个结构型的枚举项,告诉你几个关键信息好吧,枚举类型名称叫"Command",当前枚举项排行第一,名字叫"Set”,共有两个字段。好了,写去吧。写好了?过程中没什么意外发生吧,我检查检查,em……,Ok,在此基础上给你第二个任务 (这里的返回类型是SerializeStructVariant,之后再谈);
  2. serialize_field,接着序列化结构中的字段,字段名为”key”,实际内容也给你,写去吧。写好了?我检查检查,em……,Ok,在此基础上给你第三个任务;
  3. serialize_field,还是序列化结构中的字段,字段名为”value”,实际内容也给你,写去吧。写好了?我检查检查,em……,Ok,在此基础上给你第四个任务;
  4. end,结束了,没什么数据给你了,你最后收拾收拾,给小作文润色润色吧。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// ... 
// after Command::Set was matched
let mut __serde_state = match _serde::Serializer::serialize_struct_variant(
__serializer,
"Command",
0u32,
"Set",
0 + 1 + 1,
) {
_serde::export::Ok(__val) => __val,
_serde::export::Err(__err) => {
return _serde::export::Err(__err);
}
};
match _serde::ser::SerializeStructVariant::serialize_field(
&mut __serde_state,
"key",
key,
) {
_serde::export::Ok(__val) => __val,
_serde::export::Err(__err) => {
return _serde::export::Err(__err);
}
};
match _serde::ser::SerializeStructVariant::serialize_field(
&mut __serde_state,
"value",
value,
) {
_serde::export::Ok(__val) => __val,
_serde::export::Err(__err) => {
return _serde::export::Err(__err);
}
};
_serde::ser::SerializeStructVariant::end(__serde_state);

我们看到,serde::Serialize没有想我们预想中的那样,在内存中新开辟空间来存放”中间类型”数据,而是把中间类型体现在对serializer的使用上,Serialize看出来,当前数据属于struct_variant,所以直接调用serialize中对应的方法。同理,对Command::Rm,调用的是newtype_variant,对Command::Quit调用的是unit_variant

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Command::Rm(ref __field0) => {
_serde::Serializer::serialize_newtype_variant(
__serializer,
"Command",
2u32,
"Rm",
__field0,
)
}

Command::Quit => _serde::Serializer::serialize_unit_variant(
__serializer,
"Command",
3u32,
"Quit",
)

所以,serde data modelserde::Serializeserde::ser::Serializer共同承认的一套概念。serde::ser::Serializer用方法的形式,明确自己对某个data model的处理方式。而serde::Serialize则负责观察数据,将其分类,告知、提示serde::ser::Serializer该怎么做,即调用它的方法。这个过程中,数据只有一份。所以,如果对设计模式熟悉,这可以看成是访问者模式的一种应用。

从serde::ser::Serializer的角度

到这里,我们该看serialzier具体是怎么写作文的了。

第一个任务,serialize_struct_variant。根据json,Command::Set{ key: "A", val: "42" }表现为{"Set":{"key":"A","value":"42"}},所以忽略名称"Command" 以及枚举项的位置。作文起手,单走一个{,接一手字符串&str“Set”的序列化小作文。关于字符串的serde::Serialize已经在Serde crate内部实现,其实也很容易猜到,实现调用给定serializerserialze_str方法,为字符传类型添加引号。到这里,self.output的值为{"Set",最后再接一个":{"形成 {"Set":{

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ser.rs
fn serialize_struct_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
_len: usize,
) -> Result<Self::SerializeStructVariant> {
self.output += "{";
variant.serialize(&mut *self)?;
self.output += ":{";
Ok(self)
}

...

fn serialize_str(self, v: &str) -> Result<()> {
self.output += "\"";
self.output += v;
self.output += "\"";
Ok(())
}

如前所说,serialize_struct_variant返回的应该是一个SerializeStructVariant类型,这其实是一个trait,ser.rs为当前的serializer实现了该trait,所以返回的是Ok(self)

SerializeStructVariant类似的,有下面这些,定义在Serde中ser模组

Trait Description
SerializeMap Returned from Serializer::serialize_map.
SerializeSeq Returned from Serializer::serialize_seq.
SerializeStruct Returned from Serializer::serialize_struct.
SerializeStructVariant Returned from Serializer::serialize_struct_variant.
SerializeTuple Returned from Serializer::serialize_tuple.
SerializeTupleStruct Returned from Serializer::serialize_tuple_struct.
SerializeTupleVariant Returned from Serializer::serialize_tuple_variant.

他们共同的特点,返回自一些序列化集合类型时所用的方法。目的是支持有状态的序列化,比如你可以在返回的结构里记录接下来是否是处理集合的第一项,以此来控制序列化中分隔符的使用。

再聚焦到我们使用到的SerializeStructVariant上,必须的两个方法是serialize_fieldend,这两个方法被serde::Serialize调用过,用于安排作文写作的2、3、4步。

serialize_field中,实际上并没有记录状态,因为当前简化版只支持紧密的json格式,所以可以通过看前一个字符是不是{来判断,是否在处理集合的第一项内容,进而决定要不要加,分割。

end就更简单了,小作文用}}收尾搞定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
impl<'a> ser::SerializeStructVariant for &'a mut Serializer {
type Ok = ();
type Error = Error;

fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
if !self.output.ends_with('{') {
self.output += ",";
}
key.serialize(&mut **self)?;
self.output += ":";
value.serialize(&mut **self)
}

fn end(self) -> Result<()> {
self.output += "}}";
Ok(())
}
}

至此,serialzer.output已经变成我们想要的样子。{"Set":{"key":"A","value":"42"}}

尾声

起点,有一个,Rust的数据据结构。终点,有很多,json、yaml、toml、etc。

面对这个一对多的关系,通常都会想给Rust的数据结构做一个外层包装,然后各个下游的格式处理器,只和有限的数据结构做对接。这样逻辑上没有问题,但是在做包装的过程中,可能产生很多的内存分配、销毁、数据复制。

仔细想想,我们包装的目的,实际上就是对数据做分类,所以直接把这些类别用函数的形式固定,避免在内存中再造数据结构的计算开销。serde data model把Rust的数据类型分为29种,对应到Serializer::serialze_*()系列方法,搭配访问者模式,保证数据只在一个地方,然后用Serialize去驱动,帮助serializer完成对数据的序列化。

通过Serialize过程的观察,我们可以得到这样一条脉络,因为关注性能,所以要保证尽量减少新数据的产生,所以用访问者模式,由serde::Serialize带领serializer对原始数据参观一圈,一边走,一遍告诉serializer,这是A,这是B,然后小作家、小画家serializer就据此构建自己的作品。参观完,serialzer的作品也大功告成。

好了,Serialize就讲到这里。下期Deserialize再见。