Saving a file with serde and generic

Here is some code that works just fine. The goal is to save to a file, vectors of a variety of types.

use std::fs::File;
use std::io::prelude::*;
use serde_json;

fn main() {
    let strvec: Vec<String> = vec!["Oak".to_string(), "Pine".to_string(), "Doug Fir".to_string()];
    let u64vec: Vec<u64> = vec![23,56,7895];
    let f64vec: Vec<f64> = vec![2.3,56.089,-0.7895];

    let fname = "alist.lst";

    save_as_json_list(&f64vec, &fname);
    println!("\n File saved!!");
}

fn save_as_json_list(list: &Vec<f64>, fname: &str) {
    let list_as_json = serde_json::to_string(list).unwrap();

    let mut file = File::create(fname).expect("Could not create file!");

    file.write_all(list_as_json.as_bytes())
        .expect("Cannot write to the file!");
}

If I want to save the strvec vector I have to change the save_as_json_list() function to take a string vector. Like this:

       fn save_as_json_list(list: &Vec<String>, fname: &str)

Generics and I just don't get along, but I'm going to keep trying and maybe, with some help, I'll get it right someday. Anyway, I tried this:

fn save_as_json_list<T>(list: &Vec<T>, fname: &str) {
// No changes
}

and got this error:

error[E0277]: the trait bound `T: Serialize` is not satisfied
    --> src/main.rs:18:46
     |
18   |     let list_as_json = serde_json::to_string(list).unwrap();
     |                        --------------------- ^^^^ the trait `Serialize` is not implemented for `T`

Is there a way for me to accomplish this or am I barking up the wrong tree?

You should require that T implements the Serialize trait:

fn save_as_json_list<T: Serialize>(list: &Vec<T>, fname: &str) {
// No changes
}

or the long form:

fn save_as_json_list<T>(list: &Vec<T>, fname: &str)
where
    T: Serialize,
{
// No changes
}
1 Like

@alice Thank you!! So simple and it works just fine now. Of course, I have no idea what I'm doing when I "implement" the serialize trait the way you suggested. Care to try and help me understand?

You're not implementing the trait. You're requiring it to be implemented.

One way to think about it is this. When you have a generic function, this is actually an infinite list of copies, one for each possible type. For example:

fn save_as_json_list<T: Serialize>(list: &Vec<T>, fname: &str) {}

is "short-hand" for (not real syntax)

fn save_as_json_list<u32>(list: &Vec<u32>, fname: &str) {}
fn save_as_json_list<String>(list: &Vec<String>, fname: &str) {}
fn save_as_json_list<File>(list: &Vec<File>, fname: &str) {}
fn save_as_json_list<HashMap<&str, u32>>(list: &Vec<HashMap<&str, u32>>, fname: &str) {}
...

and so on, for every single type in an infinite list.

But when you add trait requirements, you a filtering the list. For example, adding where T: Serialize would remove the File case from the above list, since File doesn't implement the Serialize trait. So in the end, the list only contains types that can be serialized. This allows you to use Serialize methods inside the generic function.

2 Likes

That makes a lot of sense and answers my questions. Very clear explanation, Alice. Thank you.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.