ender maz
ender maz

Reputation: 393

serde_json with deserialize and lifetimes for generic function

Having issues with lifetimes and serde::deserialize. Tried reading the error message and tried implementing Deserialize, but then I got an error that I need to implement Deserialize.
Without lifetimes, it works

use serde::{ Deserialize, Serialize, de::DeserializeOwned};
use std::error::Error;
use std::io::{BufReader};
use std::{ fmt::Debug};
use std::{fs::File};

#[derive(Clone, Debug, Serialize, Deserialize)]
struct User<'a> {
    name: &'a str,
}

fn main(){
    let b: User = read_object("user_1.json").unwrap();
}


fn read_object<T>(path: &str) -> Result<T, Box<dyn Error>>
where
    T: DeserializeOwned + Debug,
{
    let f = File::open(&path)?;
    let reader = BufReader::new(f);

    let t: T = serde_json::from_reader(reader)?;

    Ok(t)
}

Error:

   error: implementation of `Deserialize` is not general enough
  --> src/main.rs:13:19
   |
13 |     let b: User = read_object("user_1.json").unwrap();
   |                   ^^^^^^^^^^^ implementation of `Deserialize` is not general enough
   |
   = note: `User<'_>` must implement `Deserialize<'0>`, for any lifetime `'0`...
   = note: ...but `User<'_>` actually implements `Deserialize<'1>`, for some specific lifetime `'1`

Upvotes: 4

Views: 3262

Answers (1)

kmdreko
kmdreko

Reputation: 60662

This code cannot work.

The Deserialize<'de> trait has a lifetime associated with it to support zero-copy deserialization. Essentially allows deserialized types to reference data directly from the source. See Understanding deserializer lifetimes.

When you have

#[derive(Deserialize)]
struct User<'a> {
    name: &'a str,
}

The User will have the lifetime 'a linked to the deserialization source. And name will be a reference to the string value within that source.

On the flip-side, the DeserializeOwned trait does not support zero-copy deserialization. The deserialized type must not have lifetimes associated with the Deserializer. The "owned" means it doesn't borrow from anything. The cryptic error you get is due to the way DeserializeOwned is defined, it'll be implemented for any type that implements Deserialize for any lifetime, which will exclude types expecting a particular lifetime. This bound is used when lifetimes are unecessary or impossible to guarantee.

serde_json::from_reader is one of those cases. It only deserializes JSON from sources based on the Read trait. However, the Read trait makes no guarantees that any read data is referenceable after the fact. And thus from_reader dictates that T: DeserializeOwned to avoid that problem.

To take advantage of zero-copy deserialization, you'd need to use from_slice or from_str:

let data = std::fs::read_to_string(path)?;
let user: User = serde_json::from_str(&data)?;

However, even if you do that, the data being referenced is local only to the read_object function. All data retrieved from the file is destroyed at the end of the function, and would leave user.name invalid if it were returned. The compiler would reject it (playground).

Without lifetimes, it works

And that's what I suggest you do; just use struct User { name: String }. I would advise against trying to use zero-cost deserialization from JSON strings at all because of escape sequences.

Upvotes: 9

Related Questions