rust 使借出迭代器类型适应serde样式的Map访问器

0sgqnhkj  于 2023-04-12  发布在  其他
关注(0)|答案(1)|浏览(106)

我正在尝试将我设计的数据模型适应serde MapAccess接口。我有自己的数据模型,它有一个包含键值对的List类型,如下所示:

impl List {
    fn next_item(&mut self) -> Option<(String, Item<'_>)>
}

换句话说,Item类型可变地从List对象借用,并且必须在从List提取下一个Item之前完全处理。
我需要使这个数据模型适应一个看起来像这样的反序列化器trait:

trait MapAccess {
    fn next_key<T: Deserialize>(&mut self) -> Option<T>;
    fn next_value<T: Deserialize>(&mut self) -> T;
}

这两个方法应交替调用;如果你没有按照正确的顺序调用它们,它们会被允许panic或者返回垃圾结果。这个trait是由我正在使用的反序列化器库提供的,也是这个问题中唯一一个我无法控制或修改的。
我试图将List&mut List转换为MapAccess,但遇到了一些问题。我确实有一个函数可以将Item<'_>转换为T: Deserialize(特别是结束List的借用):

fn deserialize_item<T: Deserialize>(item: Item<'_>) -> T;

我遇到的基本问题是MapAccess::next_key调用List::next_item,然后将String反序列化为键,然后需要存储Item<'_>,以便next_value可以处理它。(两个字段都可变地借用相同的东西)。这不是真正的自我引用,所以我不是我最接近的是某种类似于enum State {List(&'a mut List), Item(Item<'_>)}的枚举(因为我不需要访问一个List,而我需要访问一个List)。我正在使用Item,但是我还没有找到一种方法,可以在Item被使用后,为下一个next_key恢复对List的访问。
作为数据模型的作者,我确实有一些余地来改变它的设计方式,但是 * 如果可能的话 * 我想保留某种借用模型,我用它来加强解析的一致性(即借用防止X1 M22 N1 X解析下一项,直到X1 M23 N1 X完全解析当前项的内容之后。当然,我可以设计任何其他类型来正确地实现它。
注意:这些都是serde相关的,但是我使用原始类型来简化接口。特别是我省略了所有的Result类型和'de生存期,因为我相信它们与这个问题无关。
编辑:这是我的“尝试”来做一些有用的东西。这没有编译,但希望它能理解我想在这里做的事情。

fn deserialize_string<T: Deserialize>(value: String) -> T { todo!() }
fn deserialize_item<T: Deserialize>(item: Item<'_>) -> T { todo!() }

struct ListMapAccess<'a> {
    list: &'a mut List,
    item: Option<Item<'a>>,
}

impl<'a> MapAccess for ListMapAccess<'a> {
    fn next_key<T: Deserialize>(&mut self) -> Option<T> {
        let (key, item) = self.list.next_item()?;
        self.item = Some(item);
        Some(deserialize_string(key))
    }

    fn next_value<T: Deserialize>(&mut self) -> T {
        let item = self.item.expect("Called next_item out of order");
        deserialize_item(item)
    }
}

现在,我真的完全不依赖于ListMapAccess结构体;我对任何将我的List/Item接口集成到MapAccess的解决方案都很满意。

oprakyz7

oprakyz71#

这似乎是不可能的安全生 rust ,但我已经想出了这个不安全的解决方案。

// `List` is a type that I control that I'd like to adapt to
// `MapAccess`.
impl List {
    fn next_item(&mut self) -> Option<(String, Item<'_>)>
}

// Fake version of `serde::de::MapAccess`. I don't have control
// of this trait.
trait MapAccess {
    fn next_key<T: From<String>>(&mut self) -> Option<T>;
    fn next_value<T: From<Item>>(&mut self) -> T;
}

struct ListMapAccess<'a> {
    fake_list: PhantomData<&'a mut List>,
    list: NonNull<List>,
    item: Option<Item<'a>>,
}

/// Returned if you call `next_item` while `item` is not `None`
/// or if you called `use_item` while it *is* `None`
struct BorrowError;

impl<'a> ListMapAccess<'a> {
    fn next_item(&mut self) -> Result<Option<String>, BorrowError> {
        if self.item.is_some() { return Err(BorrowError) }

        // SAFETY: It's only safe to create this reference if the `item` is
        // empty, because the `item` borrows from the list. If `item` is
        // empty, then there are no "real" borrows of `List` in existence,
        // since we're pretending to own a mutable borrow in `ListMapAccess`.
        let list: &'a mut List = unsafe { self.list.as_mut() };
        let Some((key, item)) = list.next_item() else { return Ok(None) };

        self.item = Some(item);
        Ok(Some(key))
    }

    fn use_item<R>(
        &mut self,
        // The use of `for<'b>` here ensures that `op` can't
        // cause the `Item` to outlive `ListMapAccess`.
        op: impl for<'b> FnOnce(Item<'b>) -> R
    ) -> Result<R, BorrowError> {
        self.item.take().map(op).ok_or(BorrowError)
    }
}

impl MapAccess for ListMapAccess<'_> {
    fn next_key<T: Deserialize>(&mut self) -> Option<T> {
        self.next_item()
            .expect("called next_key out of order")
            .map(|key| key.into())
    }

    fn next_value<T: Deserialize>(&mut self) -> T {
        self.use_item(|item| item.into())
            .expect("called next_value out of order")
    }
}

这里的关键是ListMapAccess必须拥有一个NonNull<List>,而不是一个可变的引用。我们提供了一个API,它保证在编译时列表永远不会被可变地借用超过一次,因为它只允许在self.item(固有地借用列表)为空时创建可变借用,并防止ListMapAccess逃脱所有权。
这里的代码是真实世界用例的简化(它避免了一堆与手头问题无关的额外生命周期和Result返回类型),但对于感兴趣的人来说,这里提供了真实世界版本。

相关问题