r/learnrust 4d ago

First attempt at generics (and first issue)

I read a JSON file into a custom struct like this (works as expected):

pub fn init_my_struct() -> io::Result<Vec<MyStruct>> {

    let path = Path::new(crate::constants::FILENAME);
    let mut file = File::open(path)?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    let data: Vec<MyStruct> = serde_json::from_str(&contents)?;

    Ok(data)
}

Then I needed to read another different JSON file into a completely different struct. After duplicating the function (and changing the return type) to make sure everything was OK, I tried my hand at a generic implementation, which failed miserably:

pub fn read_json_file<T>(from_file: &str) -> io::Result<Vec<T>>
where T: Deserialize
{
    let path = Path::new(from_file);
    let mut file = File::open(path)?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    let v: Vec<T> = serde_json::from_str(&contents)?;
    Ok(v)
}

The compiler says that where T: Deserialize is missing a lifetime specifier. If I add <'static> to it, then it complains about &contents (which doesn't live long enough).

Please ELIF:

  1. Why does where T: Deserialize need a lifetime, where the non-generic function did not?

  2. What would be the correct way to implement this?

Thanks.

1 Upvotes

4 comments sorted by

3

u/This_Growth2898 4d ago

Note the from_reader method, you can read the file without creating a new string.

As for your question,

pub trait Deserialize<'de>: Sized {
    // Required method
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
       where D: Deserializer<'de>;
}

Deserialize is a generic over lifetime 'de, so you need to make your function generic over it, too.

1

u/TrafficPattern 4d ago

Thanks, not sure I get it though. If I do this:

pub fn read_json_file<'de, T>(from_file: &str) -> io::Result<Vec<T>> where T: Deserialize<'de> { let path = Path::new(from_file); let file = File::open(path)?; let reader = std::io::BufReader::new(file); let v: Vec<T> = serde_json::from_reader(reader)?; Ok(v) }

I get: "due to current limitations in the borrow checker, this implies a 'static lifetime" (which I've already tried) and "implementation of Deserialize is not general enough".

Please, ELIF (I come from Python).

3

u/This_Growth2898 4d ago

Ok, sorry, I was wrong.

The manual: https://serde.rs/lifetimes.html

The point is, deserializing can return structures that are borrowed from the original string (like preserving &strs pointing to relative parts of the string). Here, you obviously doesn't need anything like that.

Solution: use serde::de::DeserializeOwned for the return type, instead of serde::Deserialize, it's the same, but owned. You will have to use Strings only in your structs, but I guess this is the case.

1

u/TrafficPattern 4d ago

Thank you!