Serde deserialize, closure, lifetime

#1
  1. I have the following code:
fn setup_ws_handler<'a, ToClient>(url: &str, sender: mpsc::Sender<ToClient>) where
    ToClient: serde::Deserialize<'a> + 'static + Clone
{

    let ws = WebSocket::new(url).unwrap();

    { let url = url.to_string();
        ws.add_event_listener( move |_: SocketOpenEvent| {
            console!(log, format!("connection opened: {}", url) ); } ); }

    { let url = url.to_string();
        ws.add_event_listener( move |_: SocketErrorEvent| {
            console!(log, format!("connection errored: {}", url) ); } ); }

    { let url = url.to_string();
        ws.add_event_listener( move |event: SocketCloseEvent| {
            console!(log, format!("connection closed: {} : {:?}", url, event.reason())); } ); }

    ws.add_event_listener( move |event: SocketMessageEvent| {
        let data = event.data().into_text().unwrap();
        let to_client: ToClient = serde_json::from_str(&data).unwrap() ;
        sender.send(to_client.clone());
    });

}
  1. It is generating the following error:
   |
18 | fn setup_ws_handler<'a, ToClient>(url: &str, sender: mpsc::Sender<ToClient>) where
   |                     -- lifetime `'a` defined here
...
38 |         let to_client: ToClient = serde_json::from_str(&data).unwrap() ;
   |                                   ---------------------^^^^^-
   |                                   |                    |
   |                                   |                    borrowed value does not live long enough
   |                                   argument requires that `data` is borrowed for `'a`
39 |         sender.send(to_client.clone());
40 |     });
   |     - `data` dropped here while still borrowed
  1. I am confused for the following reason:

  2. let data = ... has type String, so there is no lifetime issue.

  3. I deserialize it to a ToClient. Why do I get an error here?

  4. Before putting it in the channel, I even do a useless clone, to make sure that the “to_client” only has to live for a very short amount of time.

#2

Use ToClient: DeserializeOwned or ToClient: for<'a> Deserialize<'a>, not a 'a selected by the caller setup_ws_handler.

1 Like
#3
  1. Here is a shorter failure case:

fn string_to_client<'a, ToClient>(s: String) -> ToClient where
    ToClient: serde::Deserialize<'a> + 'static + Clone
{
    let to_client: ToClient = serde_json::from_str(&s).unwrap() ;
    to_client
}
#4

serde::de::DeserializeOwned fixed it. Thanks!

#5

This is covered in more detail on https://serde.rs/lifetimes.html which I recommend reading if you are writing Deserialize trait bounds. In contrast to most other places in Rust, “I am missing a lifetime; better fill it in with 'static or some unconstrained lifetime parameter” tends not to be a productive strategy with Serde. <'de, T> where T: Deserialize<'de> means that T is deserializable only from data with lifetime 'de provided by the caller. If the caller isn’t also providing data with lifetime 'de along with this choice of type, then something is wrong.

1 Like
#6

@dtolnay : Thanks. So the highpoints are:

  1. to allow zero-copy deserialization, we need support for Deserialized<'de>

  2. impl<'de, T> ... where T: Deserialized<'de> means that for any choice of 'de made by te “caller”, serde_json::from_str needs to return object of liftype `‘de’

  3. However, in this case, we realy want for<'de> Deserialized<'de> which is the same as DeserializedOwned as we want the function doing the deserialixation o pick the value for de

Is the above correct?

#7

In general, Deserialize<'de> is for parse buf: &'de [u8] to produce MyStruct<'de> which may contains &'de str that actually points to subslice of buf. If you want to enforce String instead, DeserializeOwned exactly do this.