Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access field of a generic type in function

Tags:

rust

I have spent the last 4 days trying to figure this one out and I'm really really stuck. Basically I have a large number of similar structs, all containing one particular field. let's call it data of type u32:

struct A {
    data: u32,
}

struct B {
    data: u32
}

...

struct N {
    data: u32
}

What I need to do is write a function outside those structs, which takes a generic type (that is, either one of those structs) perform some manipulation on the field and return the value. Basically something along the lines of:

fn some_manipulation<T>(st: &T) -> u32 {
    st.data * 10
}

Which, as it stands, is not possible because there is no data field on type T. Another thing is I cannot modify the structs. Is there a sensible way to achieve this?

like image 383
rred Avatar asked Dec 14 '22 07:12

rred


2 Answers

So exactly as @Netwave pointed out, to make it work you need a trait that some_manipulation would use to access the data field, but to implement this trait for all the types you can use a custom macro.

Playground

I defined my macro to look like this impl_BorrowData!{u32, [A, B, N]} (where first argument is type of data field and then a list of structs follows, but macros are flexible enough and you can make it look as you wish.

trait BorrowData {
    type Output;
    fn borrow_data(&self) -> &Self::Output;
}

macro_rules! impl_BorrowData {
    ($out:ty, [$($t:ty),+]) => {
        $(impl BorrowData for $t {
            type Output = $out;
            fn borrow_data(&self) -> &Self::Output {
                &self.data
            }
        })*
    }
}

impl_BorrowData!{u32, [A, B, N]} // first the output type <u32>, then names of structs

fn some_manipulation<T: BorrowData<Output=u32>>(st: &T) -> u32 {
    st.borrow_data() * 10
}

fn main() {
    let a = A { data: 2 };
    println!("{}", some_manipulation(&a));
    let b = B { data: 50 };
    println!("{}", some_manipulation(&b));
}
like image 108
Alexey Larionov Avatar answered Jan 03 '23 09:01

Alexey Larionov


The other answers explain how to use a trait to abstract over different types, and how to use macros to automate the process for fields of the same name in different types. There are two possible improvements (or, one addition and one improvement):

  • If there is only one data field that you need to borrow, you don't need to define a custom trait, you can use the AsRef trait from the standard library, and just implement it for your types.

  • To mutate the structs, which your question also asks about, your function must accept &mut T and your trait must return &mut u32. Because of the latter you cannot reuse the access trait for mutation, you have to have two traits, or always have the possibility of mutation. The standard library provides the AsMut trait as the mutating equivalent of AsRef.

Using the standard traits and allowing for mutation, the code would look like this (playground):

impl AsRef<u32> for A {
    fn as_ref(&self) -> &u32 {
        &self.data
    }
}

impl AsMut<u32> for A {
    fn as_mut(&mut self) -> &mut u32 {
        &mut self.data
    }
}

// and the same for B, or use a macro
...

fn some_manipulation<T: AsRef<u32>>(st: &T) -> u32 {
    *st.as_ref() * 10
}

fn mutating_manipulation<T: AsMut<u32>>(st: &mut T) {
    *st.as_mut() *= 10;
}
like image 31
user4815162342 Avatar answered Jan 03 '23 10:01

user4815162342