[Rust Noob] Returning a Generic Type with serde_json

Hi! I'm coming to Rust from a JavaScript/Python background and trying to wrap my head around the type system and lifetimes.

I'm creating a daily task manager as my first little Rust project. I'm using json files to store my tasks/history for right now and I've been using separate functions to import each file. Now I'm trying to abstract this into one function and everything is falling apart.

This is the minimal version of what's been working:

use serde::{Deserialize, Serialize};
use serde_json::Result;

#[derive(Serialize, Deserialize, Clone, Debug)]
struct Task {
    // stuff
}

#[derive(Serialize, Deserialize, Clone, Debug)]
struct WorkDay {
    // more stuff
}

fn import_tasks() -> Vec<Task> {
    let mut file = File::open("tasks.json")
        .expect("Can't find tasks file");
    let mut contents = String::new();
    file.read_to_string(&mut contents);

    let tasks: Vec<WorkDay> = serde_json::from_str(&contents)
        .expect("serde error");
    tasks
}

fn import_history() -> Vec<WorkDay> {
    let mut file = File::open("history.json")
        .expect("Can't find history file");
    let mut contents = String::new();
    file.read_to_string(&mut contents);

    let history: Vec<WorkDay> = serde_json::from_str(&contents)
        .expect("serde error");
    history
}

fn main() {
    let tasks = import_tasks();
    let history = import_history();
}

Lots of repeated code that should be abstracted away. Ideally, I'd want to have a function that I could call like

let tasks: Vec<Task> = import_json_file("tasks.json");
let history: Vec<Workday> = import_json_file("history.json");

But everything I try makes the compiler angry! This is the best I've been able to do:

fn import_json_file<'a, T>(s: &'a str) -> Vec<T>
where
    T: Deserialize<'a>
{
    let mut file = File::open(s)
        .expect("failed to find file");
    let mut contents = String::new();
    file.read_to_string(&mut contents);

    let rv: Vec::<T> = serde_json::from_str(&contents)
        .expect("Serde error")
        .clone();
    rv
}

But even with that, I get the following errors:

- type annotations needed: cannot infer type of the type parameter `T` declared on the function `from_str`
- consider specifying the generic argument: `::<T>`

I'm just completely lost at this point. Is it that I'm trying to wrap the the generic type in a vector? I have no idea. Any help would be very much appreciated.

The problem is the .clone(). It's a method call, and methods are looked up based on the type of the receiver, but the receiver type isn't known here because it's “whatever serde_json::from_str() returned". Remove it, and you will solve this error.

(It would also work to take the compiler's suggestion and specify the generic argument — serde_json::from_str::<T>(&contents) — but removing the clone() is simpler and more efficient.)

Once that is resolved, you will find another problem:

error[E0597]: `contents` does not live long enough
  --> src/lib.rs:14:45
   |
5  | fn import_json_file<'a, T>(s: &'a str) -> Vec<T>
   |                     -- lifetime `'a` defined here
...
14 |     let rv: Vec::<T> = serde_json::from_str(&contents)
   |                        ---------------------^^^^^^^^^-
   |                        |                    |
   |                        |                    borrowed value does not live long enough
   |                        argument requires that `contents` is borrowed for `'a`

T: Deserialize<'a> means that the data produced from deserialization is allowed to borrow from data with lifetime 'a. But 'a is not the lifetime of the serialized data; it is the lifetime of the name of the file. In situations like this, you should use T: serde::de::DeserializeOwned. Here's the page of the serde manual about deserializer lifetimes.

Once that's also fixed, your code should compile.

5 Likes

It works!

Thank you so much! This is my first time working with a programming paradigm like this, it's a little overwhelming :face_with_spiral_eyes: