serde-rs / json

Strongly typed JSON library for Rust
Apache License 2.0
4.81k stars 552 forks source link

Generic parameter bound by `Deserialize<'de>`. Is this pattern possible? #934

Closed jukanntenn closed 1 year ago

jukanntenn commented 1 year ago

I am refactoring a simple Rust project. Some structures in the project implement a run method, which all share the same behaviour:

  1. receive a WebSocket message.
  2. deserialize the message to a Rust structure (Implement Deserialize<'de'>, not DeserializedOwned for performance consideration) via serde_json::from_str.
  3. pass the deserialized structure to a callback function.

Here is a simplified example to demonstrate the implementation currently:

use serde::Deserialize;

fn message() -> String {
    r#"{"field1":"value1"}"#.to_string()
}

#[derive(Deserialize, Debug)]
pub struct Info<'a> {
    pub field1: &'a str,
}

fn run<Callback>(mut callback: Callback)
where
    Callback: FnMut(Info),
{
    loop {
        let content = message();
        match serde_json::from_str::<Info>(&content) {
            Ok(res) => callback(res),
            Err(e) => eprintln!("Error: {}", e),
        }
    }
}

fn main() {
    run(|info: Info| {
        println!("info: {:?}", info);
    });
}

This is fine, when run this example, it outputs: info: Info { field1: "value1" }

Then I try to make function run more generic. My goal is to allow serde_json::from_str deserialize message to any structure, not just Info, so I introduce a generic parameter Res, the run function becomes:

fn run<'de, Res: Deserialize<'de>, Callback>(mut callback: Callback)
where
    Callback: FnMut(Res),
{
    loop {
        let content = message();
        match serde_json::from_str::<Res>(&content) {
            Ok(res) => callback(res),
            Err(e) => eprintln!("Error: {}", e),
        }
    }
}

But when I run the example, rust complains:

error[E0597]: `content` does not live long enough
  --> examples/demo2.rs:18:43
   |
12 | fn run<'de, Res: Deserialize<'de>, Callback>(mut callback: Callback)
   |        --- lifetime `'de` defined here
...
18 |         match serde_json::from_str::<Res>(&content) {
   |               ----------------------------^^^^^^^^-
   |               |                           |
   |               |                           borrowed value does not live long enough
   |               argument requires that `content` is borrowed for `'de`
...
22 |     }
   |     - `content` dropped here while still borrowed

Apparently, there is no lifetime issue at runtime. But since Res was bound to lifetime 'de declared in run function, so rust complains. (My understanding of lifetime here is I said Res must live long enough than the scope of fuction run, but actually it is not).

Is there any approach to work around this issue? Or other patterns to achive the same goal? Thanks for your help!

Here is the real project: yufuquant/rust-bybit: Rust API connector for Bybit's WebSockets APIs. (github.com)

jukanntenn commented 1 year ago

Sorry for disturbing, an excellent answer can be found here: generic-parameter-bound-by-deserialize-de-is-this-pattern-possible-in-rust