How does deserialize_tuple work in bincode

#1

Hello. I am trying to create a library for parsing LabVIEW binary files. Since LabVIEW does have containers like clusters and arrays it is only logical to create serde deserializer as well. So far creating one was fairly simple, except for one thing: I do not understand how to write SeqAccess implementation as my attempts ended with one lifetime-related error or another. On other hand, bincode authors have somehow managed to do just that so I ended up trying to duplicate their code without really understanding it.

So the question is, why bincode code manages to compile, but very similar my fails with

error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
   --> src\deserialize.rs:164:38
    |
164 |                     seed.deserialize(&mut *self.deserializer).map(Some)
    |                                      ^^^^^^^^^^^^^^^^^^^^^^^
    |
note: first, the lifetime cannot outlive the lifetime 'a as defined on the impl at 151:19...
   --> src\deserialize.rs:151:19
    |
151 |         impl<'de, 'a, 'b: 'a, P: LVParseLiving<'de> + 'b> SeqAccess<'de>
    |                   ^^
note: ...so that reference does not outlive borrowed content
   --> src\deserialize.rs:164:38
    |
164 |                     seed.deserialize(&mut *self.deserializer).map(Some)
    |                                      ^^^^^^^^^^^^^^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime 'de as defined on the impl at 151:14...
   --> src\deserialize.rs:151:14
    |
151 |         impl<'de, 'a, 'b: 'a, P: LVParseLiving<'de> + 'b> SeqAccess<'de>
    |              ^^^
    = note: ...so that the types are compatible:
            expected serde::Deserializer<'de>
               found serde::Deserializer<'_>

For reference, here is the relevant code:


pub trait LVParse {
    …
}

trait LVParseLiving<'a>: LVParse {
}

/// `LVDeserializer` describes serde deserializer.
struct LVDeserializer<P> {
    /// Some data reader for which parsing was implemented.
    parser: P,
}

macro_rules! create_deserialize {
    …
    ($fname:ident, $de:tt => $self:ident, $v:ident : $vt:ty, $visitor:ident
                             $code:block) => {
        fn $fname<V: Visitor<$de>>($self, $v: $vt, $visitor: V)
            -> LVResult<V::Value>
            $code
    };
    …
}

impl<'de, P: LVParseLiving<'de>> Deserializer<'de>
    for &'de mut LVDeserializer<P>
{
    type Error = LVError;
    …
    create_deserialize!(deserialize_tuple, 'de => self, size: usize, visitor {
        /// `LVArray` describes serde sequence.
        struct LVArray<'a, P: LVParse + 'a> {
            deserializer: &'a mut LVDeserializer<P>,
            size: u32,
        }

        impl<'de, 'a, 'b: 'a, P: LVParseLiving<'de> + 'b> SeqAccess<'de>
            for LVArray<'a, P>
        {
            type Error = LVError;

            fn next_element_seed<T>(&mut self, seed: T)
                -> LVResult<Option<T::Value>>
                where T: DeserializeSeed<'de>
            {
                if self.size == 0 {
                    Ok(None)
                } else {
                    self.size -= 1;
                    seed.deserialize(&mut *self.deserializer).map(Some)
                }
            }

            fn size_hint(&self) -> Option<usize> {
                Some(self.size as usize)
            }
        }

        visitor.visit_seq(LVArray {
            deserializer: self,
            size: size as u32,
        })
    });
    …
}
0 Likes

#2

I believe your Deserializer impl is too restrictive.

impl<'de, P: LVParseLiving<'de>> Deserializer<'de>
    for &'de mut LVDeserializer<P>
{

Check Bincode or https://serde.rs/impl-deserializer.html for how to structure lifetimes in the Deserializer impl correctly.

1 Like

#3

Thanks, did not think to check there when comparing code. I now see that I wrongly tied the lifetime of the deserializer borrow to lifetime of data deserialized from. If I understand correctly what has gone wrong after is that this action meant that seed.deserialize() input borrow now suddenly has given the very same lifetime of data deserialized from even though it only needed to last until the deserialize call is finished.

Also am I right that &mut *foo trick is only there to create a borrow with smaller lifetime out of borrow with larger since with the input being actually not a borrow (it is D where D: Deserializer<'de> there, not something like &mut D) Rust can’t do this on its own?

I also see that bincode specifies a lot more lifetimes than needed (at least, with current Rust version). At least, after the following patch tests still work:

diff --git a/src/de/mod.rs b/src/de/mod.rs
index 5dc9d86..05268df 100644
--- a/src/de/mod.rs
+++ b/src/de/mod.rs
@@ -258,12 +258,12 @@ where
     where
         V: serde::de::Visitor<'de>,
     {
-        struct Access<'a, R: Read + 'a, O: Options + 'a> {
+        struct Access<'a, R: Read, O: Options> {
             deserializer: &'a mut Deserializer<R, O>,
             len: usize,
         }
 
-        impl<'de, 'a, 'b: 'a, R: BincodeRead<'de> + 'b, O: Options> serde::de::SeqAccess<'de>
+        impl<'de, 'a, R: BincodeRead<'de>, O: Options> serde::de::SeqAccess<'de>
             for Access<'a, R, O>
         {
             type Error = Error;
0 Likes