The goal is to implement the Power trait on multiple types, and it discusses potentially using macros as a shortcut (though it leaves finding a solution as an exercise to the reader). I've got a macro partially working, but I'm struggling to write a macro that would work with both reference and non reference types.
I'd like to have the macro also handle the case for &u32, but to do that I'd need to be able to deref the &u32 exponent.
Is there a way to make a conditional within the macro to say "if this type is a reference/implements deref, use the code path that calls self.pow(*other.into()), otherwise use the path that calls self.pow(other.into())"
Unfortunately, macros don't have any access to type information. Look at it a different way, though: the implementation you're thinking of requires that, for a given type T to implement, *T must be Into<u32>, and, since into takes ownership of self, *T must also be Copy so we can move out of T -> *T. This is a good use of a generic/blanket impl:
Thank you, both of you. I did have two follow up questions:
@suaviloquence
I like this solution, as it demonstrates a way to solve this without macros (though it seems to only work for reference types (I can only pass a &u32 not a u32)).
My simple thought for how I could cover both was to define two implementation, one for references, and one for non-references:
//implement for non-ref types (like u16, u32)
impl<T> Power<T> for u32
where T: Into<u32>
{
fn power(&self, other: T) -> Self {
self.pow(other.into())
}
}
// implement for ref types, (like &u16)
impl<T> Power<&T> for u32
where T: Into<u32> + Copy
{
fn power(&self, other: &T) -> Self {
self.pow((*other).into())
}
}
But this generates a compiler error as the two implementations are conflicting. Is there a better way to approach this?
@quinedot
I really like this solution too (and it gives a generic macro that works for reference and non-reference types).
I was curious, though - will the compiler optimize out the clone step when it's not needed (for example, if we are feeding in a u32 to the function)?
Also, you mentioned clone goes from T to T* - doesn't it do the reverse? Take a reference and provide an owned object?
Finally, to both of you - as someone still learning rust, is there a more "rust preferred" approach to this sort of problem? Do most people just re-implement the type manually for readability?
It isn't guaranteed but for any halfway reasonable set of constraints the answer is yes, calling clone on a copy type is equivalent to not doing so and copying it.
The most preferred approach is to avoid the need for an API that works with both T and &T, and just do one or the other based on the desired ownership semantics. But for things like the ops traits, the usual solution is (unfortunately IMO) to have the macro just spit out impls for T op T, T op &T, &T op T, &T op &T, T op= T, and T op= &T.
Technically not guaranteed, but extremely likely and so small a cost for u32 that it's not worth worrying about anyway.
When T = &U, clone goes from T to *T (from &U to U).[1]
Personally at least, it depends on how many implementations we're talking about, and how convoluted the macros have get. If I have to cover "all primitive integers" or "all common combinations of std smart pointers" or something like that, I'm going to reach for one or two macros over a dozen repetitive implementations, despite the hit to readability.
In my comment I just used the same type variables as the quote I was replying to. âŠī¸
Thank you all - I believe that answers my question!
BTW, for anyone else looking at this thread in the future, while quinedot's solution (using clone) works, if you wanted to make a macro that works for both T and &T, you can also split them within the macro: