Need help on using generics to allow functions to accept multiple types of input

Hi everyone, I have a function that I want it to accept multiple types of input so that it can be reused. Their input types are all custom Structs, containing one same field, some add operations need to be performed in the function. The following is a minimal implementation:

// Here's a demo works fine.
// get_person_info can both accept Teacher and student type.

#[derive(Debug)]
struct Student{
    id: usize ,
    name: String,
    score: f64,
}

#[derive(Debug)]
struct Teacher{
    id: usize,
    name: String,
}

fn get_person_info<T: std::fmt::Debug>(input:Vec<T>) {
    println!("{:?}" , input[0]);
}

fn main() {
    let class = vec![Student{id:0 , name: String::from("Bob") , score:80.0}];
    get_person_info(class);
}

However if I make the following change:

struct Student{
    id: usize ,
    name: String,
    score: f64,
}

struct Teacher{
    id: usize,
    name: String,
}

fn get_person_info<T: std::fmt::Debug>(input:Vec<T>) {
    println!("{:?}" , input[0].id + 10);
}

fn main() {
    let class = vec![Student{id:0 , name: String::from("Bob") , score:80.0}];
    get_person_info(class);
}

It will raise an error message that : no field id on type Trustc(E0609)

I understand that to achieve this effect you need to use the trait feature of rust, yet just as a beginner I simply do not know how to organize my code, could anyone give a sample please, thanks

One solution is to define a trait for both a teacher and a student:

trait Person {
    fn id(&self) -> usize;
    fn name(&self) -> &str;
}
impl Person for Student {
    fn id(&self) -> usize { self.id }
    fn name(&self) -> &str { &self.name }
}
impl Person for Teacher {
    fn id(&self) -> usize { self.id }
    fn name(&self) -> &str { &self.name }
}

fn get_person_info<T: Person>(input:Vec<T>) {
    println!("{:?}" , input[0].id() + 10);
}

But this has a fair bit of duplicated code. Thus, another option is to factor out the common fields of Teacher and Student into a common type. There are two methods for this: with generics and with AsRef. Here is the generic method:

struct Person<T> {
    id: usize,
    name: String,
    data: T,
}

struct Student { score: f64 }
struct Teacher {}

fn get_person_info<T>(input: Vec<Person<T>>) {
    println!("{:?}", input[0].id + 10);
}

And here is the AsRef method:

struct Person {
    id: usize,
    name: String,
}

struct Student {
    person: Person,
    score: f64,
}
impl AsRef<Person> for Student {
    fn as_ref(&self) -> &Person {
        &self.person
    }
}

struct Teacher {
    person: Person,
}
impl AsRef<Person> for Teacher {
    fn as_ref(&self) -> &Person {
        &self.person
    }
}

fn get_person_info<T: AsRef<Person>>(input: Vec<T>) {
    println!("{:?}", input[0].as_ref().id + 10);
}

These three methods all have their advantages and disadvantages, and which one to use depends on the situation and what works best in your code.

2 Likes

Thanks! One thing I didn't understand is how students and teachers inherit from persons in the part of your common_fields-generics code (if it can be called inheritance).

It's not called inheritance, it's just that Person can contain a value of any type. In that example the function can accept Vec<Person<T>> and T doesn't necessarily have to be Student or Teacher, it could be any type really - it's just that the caller is probably going to pass in either Vec<Person<Student>> or Vec<Person<Teacher>>.

1 Like