#Need help with serde Deserialize lifetimes

1 messages · Page 1 of 1 (latest)

unborn kestrel
#

Hi, I'm trying to write a generic websocket server and the messages will be generic enums. One of the enum variants will wrap a user provided type as follows:

#[derive(Deserialize, Serialize, Debug)]
enum MessageToServer<T>
where
    T: Debug,
{
    Connect,
    Data(T),
    Disconnect,
    Invalid,
    Unsupported,
}

#[derive(Deserialize, Serialize, Debug)]
enum MessageFromServer<T>
where
    T: Debug,
{
    Data(T),
    Invalid,
    Connect,
    Disconnect,
}

The problem I'm having is that now all the functions that start the server need to be generic on type T. Additionally the "Deserialize" trait requires a lifetime bound and I get the following error

error[E0310]: the parameter type `T` may not live long enough
  --> realtime/src/websocket_server/connection_handler.rs:68:5
   |
68 |     websocket.on_upgrade(move |socket| handle_socket::<T>(socket, addr))
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...so that the type `T` will meet its required lifetime bounds

I've tried setting explicit lifetime bounds for each of these functions but it doesn't seem to fix the issue. Could I get some guidance? From my brief research I see that 'static should not be used in combination with the Deserialize trait.

#

I also tried setting the trait bounds on the different functions to DeserializeOwned

unborn kestrel
#

It does not, the error I pasted in my original question is what I get when I run cargo check

#

I can make a gist to share more info

round rampart
broken thorn
round rampart
#

I'm not sure what the research showed you exactly

unborn kestrel
broken thorn
#

you should add it

unborn kestrel
#

to the enum or everywhere?

broken thorn
#

just ```rs
pub async fn websocket_handler

#
    T: Debug + DeserializeOwned + Serialize + 'static,
#

and on handle_socket probably

round rampart
unborn kestrel
#

I had to add 'static to both websocket_handler and get_router

#

and it worked

broken thorn
#

Nice

round rampart
#

you could put it on the enum

unborn kestrel
#

putting it on the enum didn't get me anything

#

I wonder why DeserializeOwned didn't work on its own

broken thorn
#

because rustc is silly :P

#

I could give you a very contrived example of why DeserializeOwned doesn’t imply 'static

#

in most real-world scenarios they are the same

#

actually no maybe it should imply 'static ferrisThink

#

hmmmmm

unborn kestrel
#

My interpretation is that DeserializeOwned just owned all the data it deserialized with no references which destroys the original serialized message. At which point I don't understand why lifetimes are involved at all.

broken thorn
#
fn demo<T: Send + Sync + DeserializeOwned>(f: impl FnOnce(&mut T)) {
    let mut data = T::deserialize(some_deserializer).unwrap();
    f(&mut data);
    std::thread::spawn(|| {
        sleep_a_while();
        println!("{data}");
    });
}

struct MyStruct<'a> {
    data: &'a str,
}
// deserializes to an empty string

fn main() {
    let mut s = String::from("hello world");
    demo::<MyStruct<'_>>(|s| {
        s.data = &s;
    });
    drop(s);
    sleep_a_long_while();
}
#

Consider this pseudocode

#

Here we have constructed a type which implements DeserializeOwned but not 'static

#

And if you thought that it implemented 'static you would result in a use-after-free as shown

round rampart
broken thorn
#

well I just provided a counterexample ferrisBut

#

Default doesn’t imply 'static for the same reason

unborn kestrel
#

and that's desirable behavior?

broken thorn
#

The code I posted does not work

#

It’s an example of a type that is DeserializeOwned but not 'static

#

And furthermore it shows that the “non-'staticness” is not artificial, because if it were assumed to be 'static that would be unsound

unborn kestrel
#

your example is a little confusing because the closure takes in s as a parameter so it's not actually pulling in s from the outer scope

round rampart
#

honestly I can't understand what the point of the example is either

#

if it was trait DeserializeOwned: 'static then demo::<MyStruct<'_>>(|s| { could only mean demo::<MyStruct<'static>>(|s| {

#

maybe there's some weird interaction when the struct isn't covariant and you want SomeStruct<'a> that you can't get from SomeStruct<'static> and it can actually be deserialized

broken thorn
#

hence explain why DeserializeOwned and 'static needs to be two separate bounds

#

Sorry, I can see why it’s a confusing explanation

unborn kestrel
#

no worries, thanks for taking time to help

round rampart
#

?play ```rs
use serde::{Deserialize, de::DeserializeOwned};

#[derive(Deserialize, Debug)]
struct MyStruct<'a> {
#[serde(skip_deserializing, default)]
data: &'a str,
#[serde(default)]
marker: std::marker::PhantomData<fn(&'a ()) -> &'a ()>,
}

fn demo<T: DeserializeOwned + 'static>() -> T {
serde_json::from_str("{}").unwrap()
}

fn main() {
let mut foo = demo::<MyStruct<'_>>();
{
let s = "a".to_string();
foo.data = &s;
dbg!(foo);
}
}

atomic burrowBOT
#
error[E0597]: `s` does not live long enough
  --> src/main.rs:19:20
   |
18 |         let s = "a".to_string();
   |             - binding `s` declared here
19 |         foo.data = &s;
   |         -----------^^
   |         |          |
   |         |          borrowed value does not live long enough
   |         assignment requires that `s` is borrowed for `'static`
20 |         dbg!(foo);
21 |     }
   |     - `s` dropped here while still borrowed

For more information about this error, try `rustc --explain E0597`.
round rampart
#

@broken thorn I actually came up with an example

#

remove the + 'static and it compiles

#

whether it's useful in any real case is questionable

broken thorn
#

Right