zakarumych / alkahest

Fantastic serialization library
Other
157 stars 9 forks source link

How to use smart pointers? #23

Open vlovich opened 5 months ago

vlovich commented 5 months ago

If I want to have a Serialize that contains an Rc<Vec>, is there a way to do this?

#[alkahest(Formula)]
struct F {
  values: Vec<u8>,
}

#[alkahest(Serialize<F>, Deserialize<'_, F>)]
struct S {
  values: Rc<Vec<u8>> // How do I map this so that an Rc is constructed around the deserialized Vec / serializes the interior of the Rc?
}
zakarumych commented 5 months ago

You may need to add generic implementation for this smartpointer. Look at implementation for Box

vlovich commented 5 months ago

Where do I find the implementation? The only place Box comes up as a string in the code is in the roundtrip test.

vlovich commented 5 months ago

Not sure if it's the cleanest way but here's what I came up with. The annoying bit is that any struct that uses Rc now needs to be broken out into an explicit formula unless there's an obvious way to connect RcFormula to Rc or I should be doing something else? Certainly nothing as elegant as Box & I couldn't find where in the code Box<T> is managed. The other problem is that deserialize_in_place is on the same trait as deserialize which means I can't define deserialize and a separate deserialize_in_place that requires T: Default, so I have to return an error.

#[derive(Formula)]
struct RcFormula<T>(T);

impl<T: SerializeRef<T> + Formula> SerializeRef<RcFormula<T>> for std::rc::Rc<T> {
    fn serialize<B>(
        &self,
        sizes: &mut alkahest::advanced::Sizes,
        buffer: B,
    ) -> std::result::Result<(), B::Error>
    where
        B: alkahest::advanced::Buffer,
    {
        <T as SerializeRef<T>>::serialize(self.as_ref(), sizes, buffer)
    }

    fn size_hint(&self) -> Option<alkahest::advanced::Sizes> {
        <T as SerializeRef<T>>::size_hint(self.as_ref())
    }
}

impl<T: Serialize<T> + Formula> Serialize<RcFormula<T>> for std::rc::Rc<T> {
    fn serialize<B>(
        self,
        sizes: &mut alkahest::advanced::Sizes,
        buffer: B,
    ) -> std::result::Result<(), B::Error>
    where
        B: alkahest::advanced::Buffer,
    {
        if let Some(inner) = std::rc::Rc::into_inner(self) {
            <T as Serialize<T>>::serialize(inner, sizes, buffer)
        } else {
            panic!("Not a unique reference and can't return an error?")
        }
    }

    fn size_hint(&self) -> Option<alkahest::advanced::Sizes> {
        <T as Serialize<T>>::size_hint(self.as_ref())
    }
}

impl<'de, T: Deserialize<'de, T> + Formula> Deserialize<'de, RcFormula<T>> for std::rc::Rc<T> {
    fn deserialize(
        deserializer: alkahest::advanced::Deserializer<'de>,
    ) -> std::result::Result<Self, alkahest::DeserializeError>
    where
        Self: Sized,
    {
        Ok(std::rc::Rc::new(<T as Deserialize<T>>::deserialize(
            deserializer,
        )?))
    }

    fn deserialize_in_place(
        &mut self,
        _deserializer: alkahest::advanced::Deserializer<'de>,
    ) -> std::result::Result<(), alkahest::DeserializeError> {
        // T might not implement Default so there's no way to get a &mut T from self.
        Err(alkahest::DeserializeError::Incompatible)
    }
}
zakarumych commented 5 months ago

I've added implementation for Box, Rc and Arc in https://github.com/zakarumych/alkahest/commit/7a45d2649b08f4ecf2a8a756be860a05e0545888

vlovich commented 5 months ago

Ow wow! Did not know expect that, thanks! I also may have 3p smart pointers (eg I use hybrid_rc although not sure it'll come up here necessarily). I'm assuming there's no good way to do this generically since I can't impl Serialize/Deserialize for external crates, so I'll need to write wrappers or explicit formulas instead?

zakarumych commented 5 months ago

Take note that Box cannot be a generic Formula due to being #[fundamental] which allows downstream crates to implement Formula for Box<DownstreamCrateLocalType> and this causes potential conflict disallowed by Rust. So if you need to serialize Box<T> field you'll need a separate type to derive Formula where field is just F where T: Serialize<F>.

Like this:

#[derive(alkahest_proc::Formula)]
struct Foo {
    a: u32,
}

#[alkahest(SerializeRef<Foo>, Deserialize<'_, Foo>)]
struct FooWithBox {
    a: Box<u32>,
}
zakarumych commented 5 months ago

hybrid_rc

You can add impls to this crate with optional dependency

Or in their crate instead :)