Search code examples
rustgeneric-programmingserde

Rust: How to restrict type parameters for derived traits


I'm trying to write a generic function that takes a Path pointing to a csv file, parses and deserializes the file to a vector of records of a certain type and returns the vector of records.

Here is my code:

[dependencies]
csv = "1.1"
serde = { version = "1.0", features = ["derive"] }

First the type-specific version that compiles fine:

use csv;
use serde::Deserialize;
use std::path::Path;

#[derive(Debug, Deserialize)]
struct Person {
    name: String,
    fav_colour: String,
}

#[derive(Debug, Deserialize)]
struct Car {
    make: String,
    year: u8,
}

fn main() {}

fn parse_csv(file_path: &Path) -> Vec<Person> {
    // Create the csv reader
    let mut csv_reader = csv::Reader::from_path(file_path).unwrap();

    // Parse the csv and collect records
    let records: Vec<Person> = csv_reader
        .deserialize()
        .map(|record: Result<Person, csv::Error>| {
            record.expect(&format!("There was a problem parsing a line"))
        })
        .collect();

    // Return records
    records
}

The parse_csv function works on the concrete Person struct.

How can I re-write this function so it accepts a generic type/struct that derives Deserialize, so that for instance it can accept either Person or Car?

Failed attempt:

fn parse_csv<T>(file_path: &Path) -> Vec<T> {
    // Create the csv reader
    let mut csv_reader = csv::Reader::from_path(file_path).unwrap();

    // Parse the csv and collect records
    let records: Vec<T> = csv_reader
        .deserialize()
        .map(|record: Result<T, csv::Error>| {
            record.expect(&format!("There was a problem parsing a line"))
        })
        .collect();

    // Return records
    records
}

produces:

error[E0277]: the trait bound `for<'de> T: _::_serde::Deserialize<'de>` is not satisfied
  --> src/main.rs:26:10
   |
26 |         .map(|record: Result<T, csv::Error>| {
   |          ^^^ the trait `for<'de> _::_serde::Deserialize<'de>` is not implemented for `T`
   |
help: consider restricting this type parameter with `T: for<'de> _::_serde::Deserialize<'de>`
  --> src/main.rs:19:14
   |
19 | fn parse_csv<T>(file_path: &Path) -> Vec<T> {
   |              ^
   = note: required because of the requirements on the impl of `_::_serde::de::DeserializeOwned` for `T`
   = note: required because of the requirements on the impl of `std::iter::Iterator` for `csv::reader::DeserializeRecordsIter<'_, std::fs::File, T>`

The compiler hints:

help: consider restricting this type parameter with `T: for<'de> _::_serde::Deserialize<'de>`

How do I go about doing that? I'm relatively new to rust, and after reading the rust book's info on traits and the where clause (where I suspect lies the answer), I still can't get this to compile.


Solution

  • You can do something along the lines of :

    use serde::de::DeserializeOwned;
    
    fn parse_csv<T>(file_path: &Path) -> Vec<T>
    where
        T: DeserializeOwned,
    {
      /// deserialization logic
    }