Search code examples
jsonrustserde

How to implement a generic serde_json::from_str


I am trying to write a generic code that reads a json file into an object. But it seems that there is something I am missing here.

use serde::Deserialize;
use std::{error::Error, fs::File, io::Read};

pub fn from_file<'a, T>(filename: String) -> Result<T, Box<dyn Error>>
where
    T: Deserialize<'a>,
{
    let mut file = File::open(filename)?;
    let mut content: String = String::new();
    file.read_to_string(&mut content)?;
    Ok(serde_json::from_str::<T>(&content)?)
}

I am getting the following error

error[E0597]: `content` does not live long enough
  --> src\util\file.rs:11:34
   |
4  | pub fn from_file<'a, T>(filename: String) -> Result<T, Box<dyn Error>>
   |                  -- lifetime `'a` defined here
...
11 |     Ok(serde_json::from_str::<T>(&content)?)
   |        --------------------------^^^^^^^^-
   |        |                         |
   |        |                         borrowed value does not live long enough
   |        argument requires that `content` is borrowed for `'a`
12 | }
   | - `content` dropped here while still borrowed

For more information about this error, try `rustc --explain E0597`.
error: could not compile `flavour` due to previous error

What I understand is that I have to bound T, thanks for this SO question. But I am uncertain which bounds to add.

And how to infer the required bounds for such problems. I tried to read from_str and found that it requires T: de::Deserialize<'a> only.


Solution

  • The lifetime argument to Deserialize<'de> indicates the lifetime of the data you're deserializing from. For efficiency reasons, serde is allowed to borrow data straight from the structure you're reading from, so for example if your JSON file contained a string and the corresponding Rust structure contained a &str, then serde would borrow the string directly from the JSON body. That means that the JSON body, at least for Deserialize, has to live at least as long as the structure deserialized from it, and that length of time is captured by the 'de lifetime variable (called 'a in your example).

    If you want to read data without borrowing anything from it, you're looking for DeserializeOwned. From the docs,

    Trait serde::de::DeserializeOwned

    A data structure that can be deserialized without borrowing any data from the deserializer.

    This is primarily useful for trait bounds on functions. For example a from_str function may be able to deserialize a data structure that borrows from the input string, but a from_reader function may only deserialize owned data.

    So if your bound is DeserializeOwned (which is really just for<'de> Deserialize<'de>), then you can read data from the string without borrowing anything from it. In fact, since you're reading from a file, you can use from_reader, which does so directly, and you don't even have to worry about the intermediate string at all.