Posts
Materials
EN
Gray Wood
Posts
Materials
EN
Gray Wood

前言

说来惭愧,搞 Rust 以来一直也只能写一些业务级别的代码,库级别的代码写不来。因此专门开一个新的系列 “从库学 Rust”,希望通过学习实现 Rust 一些库的 Demo 版本来提高对 Rust 的理解与编码水平。

本文实现了一个简化版本的 miniserde,在原本的基础上做出以下简化:

  • 重点考虑实现序列化 API 为 to_tokens,反序列化 API 为 from_tokens
  • 只支持 bool, i32, String 与 struct;
  • 针对 Token,而非字符串;
  • 不考虑性能、合法性校验等;

本文源码保存在了 Github

本文的 TODO:

  • 读 serde 源码后加以比较;

Serialize

整体思路

/learn-rust-by-lib-miniminiserde/serialize.png

整体上,为类型实现 Serialize trait,使得它们有能力转为 Fragment,不同 Serializer(如 JSON Serializer)基于 Fragment 做序列化结果计算。

Serialize 基础类型

对于基础类型 i32, bool, String,直接将自身的值挂到 Fragment 上。如以下对于 bool 类型的实现。

1
2
3
4
5
6
7
8
9
10
pub enum Fragment {
    Boolean(bool),
    // ...
}

impl Serialize for bool {
    fn begin(&self) -> Result<Fragment> {
        Ok(Fragment::Boolean(*self))
    }
}

Serialize struct

对于 struct,实现 通过 Serialize macro,使得可以通过 derive(Serialize) 的方式将自身转为 Fragment::Map(Map),这里的 Map 是一个迭代器,通过 next 方法能够拿到 (String, Serialize) KV 对,对应于结构体每一项的 key 与 value。

具体实现上,Map 记录了:

  • 结构体自身的引用;
  • 迭代器内部枚举下标;
1
2
3
4
5
struct __Map<'a> {
    // 结构体值自身引用,#ident 是 结构体名
    data: &'a #ident,
    cur: usize
}

Map 实现 Map trait。由于是写在 macro 里的,直接枚举 key 即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
impl<'a> ser::Map for __Map<'a> {
    fn next(&mut self) -> Option<(String, &dyn Serialize)> {
        match self.cur {
            #(#indexes => {
                self.cur += 1;
                // 枚举结构体 key
                let key = #field_name_list;
                let value = &self.data.#field_ident_list;
                Some((key.to_owned(), value))
            },)*
            _ => None
        }
    }
}

从这里也可以看出 Map 使用迭代器的好处:外部调用者只需要能够获取 KV 对,并不需要感知内部的具体结构。使用迭代器一来把内部复杂的结构屏蔽了(也许并不复杂= =),二来按需取值,不需要一次性返回,有利于优化内存效率。

to_tokens 实现

到这里 to_tokens 的实现就比较明显了:

  • 对于基础类型,直接取值转为 Token
  • 对于 struct,遍历迭代器取 key-value,然后对 value 再做递归;
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
// UnsafeRcVec<T> 是对 Vec<T> 的封装,使得能够在 Rc 包裹下同时持有 &mut
fn to_token_impl(v: &dyn Serialize, mut vec: UnsafeRcVec<Token>) -> Result<()> {
    let fragment = Serialize::begin(v)?;

    match fragment {
        Fragment::I32(x)     => { vec.push(Token::I32(x)); }
        Fragment::Boolean(x) => { vec.push(Token::Boolean(x));  }
        Fragment::String(x)  => { vec.push(Token::String(x)); }
        Fragment::Map(mut map) => {
            let mut should_push_comma = false;

            vec.push(Token::LB);
            while let Some((key, v)) = map.next() {
                if should_push_comma {
                    vec.push(Token::Comma);
                }
                should_push_comma = true;

                vec.push(Token::String(key));
                vec.push(Token::Colon);
                to_token_impl(v, vec.clone())?;
            }
            vec.push(Token::RB);
        }
    }

    Ok(())
}

Deserialize

整体思路

/learn-rust-by-lib-miniminiserde/deserialize.png

整体上,为支持类型实现 Deserialize trait,使得不同的 Deserializer 有能力通过统一的 Visitor trait 向属于这一类型的存储变量内部写入反序列化结果。

PlaceStore

PlaceStore 是一个允许同时持有并写入的结构,其是核心是 Rc<Option<T>>,由 Rc 提供同时持有的能力。当有了 PlaceStore 后,就可以赋予 Visitor 能力,此时 PlaceStore 被外围方法和自身(作为 Visitor)同时持有,由 Visitor 在工作流程中写入值,外围函数在工作结束后将值取出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pub struct PlaceStore<T> {
    out: Rc<Option<T>>,
}

impl<T> PlaceStore<T> {
    pub fn set(&mut self, value: T) {
        let inner = unsafe { Rc::get_mut_unchecked(&mut self.out) };
        *inner = Some(value);
    }
    pub fn try_unwrap(mut self) -> Result<T> {
        let out = unsafe { Rc::get_mut_unchecked(&mut self.out) };
        out.take().ok_or(Error)
    }
    pub fn clone(&self) -> Self {
        Self {
            out: Rc::clone(&self.out),
        }
    }
}

Visitor

Visitor trait 代表了向内部写入值的能力,在默认实现中,将所有实现都置为未实现,由实现方做方法实现覆盖。

这里的 Visitor 确实就是设计模式中的 访问者模式,面向了一组不同类型存储对象提供写入自身的能力。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pub trait Visitor {
    fn boolean(&mut self, _x: bool) -> Result<()> {
        unimplemented!()
    }
    fn i32(&mut self, _x: i32) -> Result<()> {
        unimplemented!()
    }
    fn string(&mut self, _x: &str) -> Result<()> {
        unimplemented!()
    }
    fn map(&mut self) -> Result<Box<dyn Map>> {
        unimplemented!()
    }
}

make_place!

由于 PlaceStore 后续需要 impl Visitor,但对于外部 crate,引入 PlaceStore 实现 Visitor trait 违背孤儿规则,因此,这里可以编写一个 macro make_place!PlaceStore 包一层。

1
2
3
4
5
6
7
8
#[macro_export]
macro_rules! make_place {
    ($name:ident) => {
        pub struct $name<T> {
            pub out: PlaceStore<T>,
        }
    };
}

这样对于外部 crate,就可以编写代码如:

1
2
3
4
5
make_place!(_Place);

impl Visitor for _Place {
    // ...
}

同时为了方便本 crate 的使用,写下 make_place!(Place) 以后续对 Place 实现 Visitor trait。

Deserialize 基础类型

这里以 bool 为例。对 bool 实现 Deserialize trait,其内部对 Place<bool> 实现 Visitor trait,在 Visitor trait 中覆盖 boolean 方法,使得此方法调用时可将值写入外部传入的 PlaceStore 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
impl Deserialize for bool {
    fn begin(out: PlaceStore<Self>) -> Box<dyn Visitor> {
        impl Visitor for Place<bool> {
            fn boolean(&mut self, x: bool) -> Result<()> {
                self.out.set(x);
                Ok(())
            }
        }

        let place = Place { out };
        Box::new(place)
    }
}

Deserialize struct

对于 struct,重点在于对 Map trait 的实现上。

内部构造 __Map,其枚举 struct 的每一个字段,并分别配上一个 PlaceStore。另外,还需要维护自身对应的 PlaceStore

1
2
3
4
struct __Map {
    #(#field_ident_list: PlaceStore<#field_ty_list>,)*
    __out: PlaceStore<#ident>
}

这样当外部调用 key 方法时,可以通过 key 对应的 PlaceStore 创建对应的 Visitor
当外部调用 finish 方法时,将各字段对应的 PlaceStore 中的值提取出来并构造自身值,最后写入自身的 PlaceStore 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
impl de::Map for __Map {
    fn key(&mut self, key: &str) -> Result<Box<dyn Visitor>> {
        match key {
            #(#field_name_list => {
                let out = self.#field_ident_list.clone();
                Ok(Deserialize::begin(out))
            },)*
            _ => {
                return Err(Error)
            }
        }
    }
    fn finish(&mut self) -> Result<()> {
        #(
            let #field_ident_list = self.#field_ident_list.clone().try_unwrap()?;
        )*
        self.__out.set(#ident {
            #(#field_ident_list),*
        });

        Ok(())
    }
}

from_tokens 实现

在核心函数 from_tokens_impl 中,假定已经实现了 Deserializer,其 event 方法能够获取接下来的状态:

  • 是基础类型时,直接调用 Visitor 对应方法写入;
  • 是 "{",即对象时,调用 vistor.map 创建 Map,后在遍历中先通过解析 key 得到 value 应当写入的 Visitor,再通过递归完成对 value 的写入,直到遇到 "}" 时完成对整个 Map 的写入;
  • 无可读 Token,则成功结束;

在外围函数 from_tokens 中,创建对应的 PlaceStoreVisitor 以调用 from_tokens_impl,结束后取出 PlaceStore 的值并返回即可。

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
36
37
38
39
40
41
42
43
44
45
pub fn from_tokens<'a, T: Deserialize>(tokens: &'a Vec<Token>) -> Result<T> {
    let out: PlaceStore<T> = Default::default();
    let mut de = Deserializer::new(tokens);
    let visitor = T::begin(out.clone());

    from_tokens_impl(&mut de, visitor)?;

    let out = out.try_unwrap()?;
    Ok(out)
}

fn from_tokens_impl<'a>(de: &mut Deserializer, mut visitor: Box<dyn Visitor>) -> Result<()> {
    match de.event()? {
        Event::Primitive(token) => match token {
            Token::I32(x) => {
                visitor.i32(x)?;
            }
            // ...
        },
        // Token::LB ({)
        Event::MapStart => {
            let mut map = visitor.map()?;

            'map_start_loop: loop {
                let key = match de.parse() {
                    Token::String(x) => Ok(x),
                    Token::RB => break 'map_start_loop,
                    _ => Err(Error),
                }?;
                de.parse(); // :

                let visitor = map.key(&key)?;
                from_tokens_impl(de, visitor)?;
                if let Token::Comma = de.peek() {
                    de.parse(); // ,
                }
                if let Token::RB = de.peek() {
                    map.finish()?;
                }
            }
        }
        Event::End => return Ok(()),
    };
    Ok(())
}

其他知识点

Cow

Cow 是 clone-on-write 的智能指针,

  • 当使用 Cow::from(&...),或使用 Cow::Borrowed(&...) 时,只会与被借用方共享数据;
  • 当使用 (...).to_mut() 时若当前没有数据的所有权,则会 clone 一份被借用方的数据,若已持有则不会 clone;

读多写少 的场景下,使用 Cow 能够提高性能。miniserde 在 Serialize String 时使用了 Cow:

1
2
3
4
5
impl Serialize for String {
    fn begin(&self) -> Fragment {
        Fragment::Str(Cow::Borrowed(self))
    }
}

String 与 &str 的 Deref coercion

由于 String 内部实现了

1
2
3
4
impl ops::Deref for String {
    type Target = str;
    // ...
}

因此将 &String 类型传递给 &str 类型时,编译器会做隐式强制转换。下列代码是合法且常用的:

1
2
3
4
5
6
7
8
9
fn fetch(url: &str) {
    // ...
}
fn main() {
    // 从某处获取的 url
    let url = format!("https://{}/about", "www.google.com");
    // Okay, 将 &String 传递给 &str
    fetch(&url);
}

扩展生命周期

core::mem::transmute 用于自行解释字节,如:

1
2
3
4
5
6
7
fn main() {
    let v: [i8; 4] = [0x12, 0x34, 0x56, 0x78];
    // [i8; 4] 与 i32 都是 4 字节
    let x: i32 = unsafe { core::mem::transmute(v) };

    assert_eq!(x, 0x78563412);
}

在 miniserde 中,定义 extend_lifetime!(EXPR as TYPE) 宏展开为 std::mem::transmute::<TYPE, TYPE>(EXPR) 以扩展生命周期,如:

1
2
3
4
5
6
fn map(&mut self) -> Result<Box<dyn Map + '_>> {
    let mut value = NonuniqueBox::new(None);
    // ptr 被推导为 &mut Option<T>
    let ptr = unsafe { extend_lifetime!(&mut *value as &mut Option<T>) };
    // ...
}

自定义的 NonuniqueBox

Box 定义为:

1
pub struct Box<T: ?Sized, A: Allocator = Global>(Unique<T>, A);

其中 Unique 是类似于 NonNull 对 raw pointer 的封装。不同于 NonNull,持有 Unique<T> 则被认为独自占有 pointer 所指向的数据,这也是 Unique 名字的由来。

Box, Unique 的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
pub struct NonNull<T: ?Sized> {
    pointer: *const T,
}

pub struct Unique<T: ?Sized> {
    pointer: *const T,
    // NOTE: this marker has no consequences for variance, but is necessary
    // for dropck to understand that we logically own a `T`.
    //
    // For details, see:
    // https://github.com/rust-lang/rfcs/blob/master/text/0769-sound-generic-drop.md#phantom-data
    _marker: PhantomData<T>,
}

可以看出 Unique 多了一个属性 _marker: PhantomData<T>PhantomData<T> 的存在让编译器知晓这一数据类型逻辑上拥有 T,而不是仅仅拥有一个指向 T 类型的指针,这使得:

  • std 中为 Unique 实现了 Sync, Send
  • Drop 检查器(dropck, drop checker)在某些情况下会做 drop 检查;

miniserde 中定义了 NonuniqueBox,对比于 Box,其包含的是 NonNull 而不是 Unique。从语义上,这使得可以将 &mut 引用转移到其他地方,通过这一引用写入数据,最后再 drop 这个 NonuniqueBox。(实际上,笔者将 NonuniqueBox 换成 Box 后依然可以编译通过并通过原有的测试。)

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
// 原本代码的注释:
// Like Box<T>, but holds NonNull<T> instead of Unique<T> to defer asserting
// uniqueness throughout the lifetime of the object until Drop is called. This
// makes it possible to take a &mut reference to the heap allocation, then move
// the NonuniqueBox, then write to the heap allocation through that old
// reference, then drop the NonuniqueBox.
pub struct NonuniqueBox<T: ?Sized> {
    ptr: NonNull<T>,
}

// ...

// 暴露 &mut
impl<T: ?Sized> DerefMut for NonuniqueBox<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        unsafe { self.ptr.as_mut() }
    }
}

// NonuniqueBox drop 时,销毁 ptr
impl<T: ?Sized> Drop for NonuniqueBox<T> {
    fn drop(&mut self) {
        let ptr = self.ptr.as_ptr();
        let _ = unsafe { Box::from_raw(ptr) };
    }
}

如 miniserde 在反序列化序列时使用了 NonuniqueBox

1
2
3
4
5
6
7
8
9
10
11
// ...
Layer::Seq(mut seq) => {
    // 取得 &mut 并转为 &mut dyn Visitor
    let element = seq.element()?;
    let next = NonNull::from(element);
    // 后续使用此 Visitor 写入
    visitor = unsafe { extend_lifetime!(next as NonNull<dyn Visitor>) };
    // 原 NonuniqueBox 保存,后续可能会再次使用
    // 当其销毁时,对应的 Visitor 也会被销毁
    de.stack.push((outer, Layer::Seq(seq)));
}

ManualDrop

ManualDrop 可以包裹变量,用于告知编译器不要 drop 掉这个变量,其将手动调用 drop。在同一结构体中若有多个变量需要手动控制 drop 顺序,可以使用时ManualDrop

在 miniserde 中,BoxSeq 在 Deserialize Box<Seq<T>> 时使用,其中 value 是存储值,seq 可能持有 value 用于写入数据。因此 drop 时需要先 drop seq,再 drop value。实现中将 seqManuallyDrop 包裹一层。0

Rust drop 时,会先调用 <T as std::ops::Drop>::drop,再按一定规则 drop 变量。因此可以在 BoxSeqDrop 中手动调用 ManuallyDrop::dropseq drop 掉。

1
2
3
4
5
6
7
8
9
10
11
12
13
struct BoxSeq<'a, T: 'a> {
    out: &'a mut Option<Box<T>>,
    value: NonuniqueBox<Option<T>>,
    // 源代码注释:
    // May borrow from self.value, so must drop first.
    seq: ManuallyDrop<Box<dyn Seq + 'a>>,
}

impl<'a, T: 'a> Drop for BoxSeq<'a, T> {
    fn drop(&mut self) {
        unsafe { ManuallyDrop::drop(&mut self.seq) }
    }
}