Как я могу заставить Serde выделять строки на арене во время десериализации?

У меня есть структура со строковыми полями. Я хотел бы контролировать, как распределяется память для строк. В частности, я хотел бы выделить их, используя что-то вроде copy_arena.

Может быть, я мог бы создать собственный ArenaString тип, но я не вижу, как получить ссылку на Arena в код десериализации, и если это возможно, тогда мне придется иметь дело с временем жизни арены, верно?


person Jason Orendorff    schedule 23.08.2018    source источник


Ответы (1)


Вот одна из возможных реализаций, в которой serde::de::DeserializeSeed используется для доступа к распределителю арены код десериализации.

В более сложном случае использования вы можете написать процедурный макрос для генерации таких имплицит.


#[macro_use]
extern crate serde_derive;

extern crate copy_arena;
extern crate serde;
extern crate serde_json;

use std::fmt;
use std::marker::PhantomData;
use std::str;

use serde::de::{self, DeserializeSeed, Deserializer, MapAccess, Visitor};

use copy_arena::{Allocator, Arena};

#[derive(Debug)]
struct Jason<'a> {
    one: &'a str,
    two: &'a str,
}

struct ArenaSeed<'a, T> {
    allocator: Allocator<'a>,
    marker: PhantomData<fn() -> T>,
}

impl<'a, T> ArenaSeed<'a, T> {
    fn new(arena: &'a mut Arena) -> Self {
        ArenaSeed {
            allocator: arena.allocator(),
            marker: PhantomData,
        }
    }

    fn alloc_string(&mut self, owned: String) -> &'a str {
        let slice = self.allocator.alloc_slice(owned.as_bytes());
        // We know the bytes are valid UTF-8.
        str::from_utf8(slice).unwrap()
    }
}

impl<'de, 'a> DeserializeSeed<'de> for ArenaSeed<'a, Jason<'a>> {
    type Value = Jason<'a>;

    fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
    where
        D: Deserializer<'de>,
    {
        static FIELDS: &[&str] = &["one", "two"];
        deserializer.deserialize_struct("Jason", FIELDS, self)
    }
}

impl<'de, 'a> Visitor<'de> for ArenaSeed<'a, Jason<'a>> {
    type Value = Jason<'a>;

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

    fn visit_map<A>(mut self, mut map: A) -> Result<Self::Value, A::Error>
    where
        A: MapAccess<'de>,
    {
        #[derive(Deserialize)]
        #[serde(field_identifier, rename_all = "lowercase")]
        enum Field { One, Two }

        let mut one = None;
        let mut two = None;
        while let Some(key) = map.next_key()? {
            match key {
                Field::One => {
                    if one.is_some() {
                        return Err(de::Error::duplicate_field("one"));
                    }
                    one = Some(self.alloc_string(map.next_value()?));
                }
                Field::Two => {
                    if two.is_some() {
                        return Err(de::Error::duplicate_field("two"));
                    }
                    two = Some(self.alloc_string(map.next_value()?));
                }
            }
        }
        let one = one.ok_or_else(|| de::Error::missing_field("one"))?;
        let two = two.ok_or_else(|| de::Error::missing_field("two"))?;
        Ok(Jason { one, two })
    }
}

fn main() {
    let j = r#" {"one": "I", "two": "II"} "#;

    let mut arena = Arena::new();
    let seed = ArenaSeed::new(&mut arena);
    let mut de = serde_json::Deserializer::from_str(j);
    let jason: Jason = seed.deserialize(&mut de).unwrap();
    println!("{:?}", jason);
}

Если выделение арены не является строгим требованием и вам просто нужно амортизировать стоимость размещения строк по множеству десериализованных объектов, Deserialize::deserialize_in_place - более лаконичная альтернатива.

// [dependencies]
// serde = "1.0"
// serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
// serde_json = "1.0"

#[macro_use]
extern crate serde_derive;

extern crate serde;
extern crate serde_json;

use serde::Deserialize;

#[derive(Deserialize, Debug)]
struct Jason {
    one: String,
    two: String,
}

fn main() {
    let j = r#" {"one": "I", "two": "II"} "#;

    // Allocate some Strings during deserialization.
    let mut de = serde_json::Deserializer::from_str(j);
    let mut jason = Jason::deserialize(&mut de).unwrap();
    println!("{:?} {:p} {:p}", jason, jason.one.as_str(), jason.two.as_str());

    // Reuse the same String allocations for some new data.
    // As long as the strings in the new datum are at most as long as the
    // previous datum, the strings do not need to be reallocated and will
    // remain at the same memory address.
    let mut de = serde_json::Deserializer::from_str(j);
    Jason::deserialize_in_place(&mut de, &mut jason).unwrap();
    println!("{:?} {:p} {:p}", jason, jason.one.as_str(), jason.two.as_str());

    // Do not reuse the string allocations.
    // The strings here will not be at the same address as above.
    let mut de = serde_json::Deserializer::from_str(j);
    let jason = Jason::deserialize(&mut de).unwrap();
    println!("{:?} {:p} {:p}", jason, jason.one.as_str(), jason.two.as_str());
}
person dtolnay    schedule 23.08.2018
comment
Проблема с Serde 1325, похоже, указывает на то, что это не будет работать рекурсивно - я недопонимаю? - person Shepmaster; 23.08.2018
comment
Вам понадобится DeserializeSeed impl на каждом уровне иерархии вложенных структур для распространения этого Allocator везде, где он нужен (здесь может быть полезен процедурный макрос), или найти способ вставить Allocator в thread_local, чтобы сделать это доступный для десериализации подразумевает этот способ. - person dtolnay; 23.08.2018
comment
вставить Allocator в thread_local - я думал в этом направлении, но мне кажется, что время жизни возвращенных строк не будет работать естественным образом, и вам придется прибегнуть к небезопасному. Но я не пробовал. - person Shepmaster; 23.08.2018