Lifetime problem with an object-safe wrapper around `serde::Deserializer`

I am trying to use erased-serde in a library where I don't know the type of the deserializer and the type of the target value at the same time, so I want to wrap the deserializer in a dynamic trait object. Unfortunately, I am encountering an issue with lifetimes that I don't know how to resolve. The playground code (with the relevant erased-serde API mocked out): https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=84c521751f97fb5f465066cbb5db805f

The error is in Deserialize::deserialize(), which would be executed in an implementation of some object-safe trait that does not know in advance what deserializer the user wants to employ. So Deserializer must necessarily stay generic-free. The compiler says that deserializer does not live long enough to call get_erased(), which I do not understand - 's in get_erased would take value of the lifetime of the local scope, and erased does not outlive it. Any ideas of how this can be fixed?

I didn't do a full analysis of the error. But when I saw:

trait ObjectSafeDeserializer<'s, 'de> {
    fn get_erased(&'s mut self) -> Box<dyn DynDeserializer<'de> + 's>
    where
        'de: 's;
}

// ...
    fn make_deserializer<'de>(bytes: &'de [u8])
    -> 
        Box<dyn ObjectSafeDeserializer<'de, 'de> + 'de> {

I suspected the ObjectSafeDeserializer<'de, 'de> had something to do with it.[1]

Then I looked closer at ObjectSafeDeserializer and didn't see a reason for the 's lifetime to be part of the trait. So I removed it.

-trait ObjectSafeDeserializer<'s, 'de> {
-    fn get_erased(&'s mut self) -> Box<dyn DynDeserializer<'de> + 's>
+trait ObjectSafeDeserializer<'de> {
+    fn get_erased<'s>(&'s mut self) -> Box<dyn DynDeserializer<'de> + 's>
     where
         'de: 's;
 }

-impl<'s, 'de, D> ObjectSafeDeserializer<'s, 'de> for D
+impl<'de, D> ObjectSafeDeserializer<'de> for D
 where
-    D: 's,
-    &'s mut D: serde::Deserializer<'de>,
+    for<'any> &'any mut D: serde::Deserializer<'de>,
{
-    fn get_erased(&'s mut self) -> Box<dyn DynDeserializer<'de> + 's>
+    fn get_erased<'s>(&'s mut self) -> Box<dyn DynDeserializer<'de> + 's>
     where
         'de: 's,
 impl Deserializer {
-    fn make_deserializer<'de>(bytes: &'de [u8]) -> Box<dyn ObjectSafeDeserializer<'de, 'de> + 'de> 
+    fn make_deserializer<'de>(bytes: &'de [u8]) -> Box<dyn ObjectSafeDeserializer<'de> + 'de> {

That's enough for the example to compile.


  1. Trait lifetime parameters are invariant and this corresponds to a &'de mut Thing<'de> I believe, which you never want. ↩︎

Thank you! It seems to be working. I agree, I messed around with lifetimes quite a bit in this example, so the result I posted was kind of weird, but the key change was the introduction of the for<'any> &'any mut D: serde::Deserializer<'de> bound. It actually worries me a little, wouldn't 'de have to also outlive 'any for that to be the case? Or is it already implied because you can't have an &'any reference on something whose lifetime doesn't outlive 'any?

Also can't say I understand, if 'de: 'any is indeed implied, why doesn't the bound &'de mut D: serde::Deserializer<'de> work - isn't it equivalent?

It's implied for the latter reason, if and only if D contains the lifetime 'de.

  • D: 'de is implied by &'any mut D being present; if 'de is part of D, that in turn implies 'de: 'any
  • 'de is a trait parameter; types can implement Deserializer<'de> regardless of any lifetimes they do (or don't) have in their own types, be they longer or shorter. So it's not implied if D doesn't contain 'de (it's not implied by &'any mut D: Trait<'de>).

The actual D you care about in the code is a

serde_json::Deserializer<SliceRead<'de>>

so it it implied in that case. But that's not a problem.

Here's the required implementation for &mut serde_json::Deserializer<_>:

impl<'de, R: Read<'de>> Deserializer<'de> for &mut Deserializer<R>

Note how it's implemented for all valid lifetimes on the &mut (implicitly, by eliding the lifetime). When R = SliceRead<'de>, the 'de: 'lifetime_on_the_mut bound is implied here too. They don't implement for &'de mut Deserizer<R>. That's good -- it would require borrowing forever, and thus probably not be at all usable, when R = SliceRead<'de>.

No, they're not equivalent. &'de mut D where 'de is part of D forces the &mut borrow of D to be for the rest of D's validity. You need the flexibility for the lifetimes to be different to avoid borrowing forever in that case. for<'any> &'any mut D allows the &mut borrow to be arbitrarily short.

1 Like

Thanks for the explanations, and for the link to your book - seems like I should read it.