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?
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));
}
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With