Reputation: 393
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
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