Reputation:
I'm working on a simple complex number example, and trying to implement ref-value/value-ref operations as follows:
use std::ops::*;
#[derive(Clone, PartialEq)]
pub struct Complex<T: Sized + Clone> {
pub re: T,
pub im: T,
}
// Ref-Ref Multiplication
impl<'a, 'b, T: Sized + Clone> Mul<&'b Complex<T>> for &'a Complex<T>
where
T: Add<T, Output = T>,
T: Sub<T, Output = T>,
&'a T: Add<&'b T, Output = T>,
&'a T: Mul<&'b T, Output = T>,
&'a T: Sub<&'b T, Output = T>,
{
type Output = Complex<T>;
fn mul(self, rhs: &'b Complex<T>) -> Complex<T> {
panic!("// Details irrelevant")
}
}
// Ref-Value Multiplication
impl<'a, 'b, T: Sized + Clone> Mul<Complex<T>> for &'a Complex<T>
where
T: 'static,
T: Add<T, Output = T>,
T: Sub<T, Output = T>,
&'a T: Add<&'b T, Output = T>,
&'a T: Mul<&'b T, Output = T>,
&'a T: Sub<&'b T, Output = T>,
{
type Output = Complex<T>;
fn mul(self, rhs: Complex<T>) -> Complex<T> {
let t = &rhs;
self.mul(t)
}
}
The ref-ref implementation works, and from what I understand it it takes in two references of differing lifetimes, and returns a complex value-type. The ref-value part is where I'm having an issue; When I compile, the error is that rhs
doesn't live long enough. I believe I know why this is already, and that is that T could hold a reference (either direct or indirectly) to rhs when the value is returned, thus rhs
goes out of scope, but T
could hold a reference to it still.
My question is how to communicate that T
will not hold some reference to rhs
in some shape or form.
Some notes on things that I've tried so far or looked at:
T
will live at least as long as T
, so I think I need something more in the lines of "at most."Upvotes: 1
Views: 182
Reputation: 602285
As suggested by Peter Hall in the comments, the easiest solution is to derive Copy
for your complex type, and implement the operations for values. For the ref-ref implementations and the ref-val implementations, you can then simply dereference the references and use the val-val implementation.
If you want to make the approach you started work, you need higher-rank trait bounds:
use std::ops::*;
#[derive(Clone, PartialEq)]
pub struct Complex<T: Clone> {
pub re: T,
pub im: T,
}
// Ref-Ref Multiplication
impl<'a, 'b, T: Clone> Mul<&'b Complex<T>> for &'a Complex<T>
where
T: Add<T, Output = T>,
T: Sub<T, Output = T>,
&'a T: Add<&'b T, Output = T>,
&'a T: Mul<&'b T, Output = T>,
&'a T: Sub<&'b T, Output = T>,
{
type Output = Complex<T>;
fn mul(self, rhs: &'b Complex<T>) -> Complex<T> {
Complex {
re: &self.re * &rhs.re - &self.im * &rhs.im,
im: &self.re * &rhs.im + &self.im * &rhs.re,
}
}
}
// Ref-Value Multiplication
impl<'a, T: Clone> Mul<Complex<T>> for &'a Complex<T>
where
T: Add<T, Output = T>,
T: Sub<T, Output = T>,
&'a T: for<'b> Add<&'b T, Output = T>,
&'a T: for<'b> Mul<&'b T, Output = T>,
&'a T: for<'b> Sub<&'b T, Output = T>,
{
type Output = Complex<T>;
fn mul(self, rhs: Complex<T>) -> Complex<T> {
let t = &rhs;
self.mul(t)
}
}
In your version, the lifetime 'b
in the ref-value implementation is chosen by the user of the trait. Since the user could use any lifetime for 'b
, rhs
would need static lifetime for your code to be valid. What you want instead is that *'a T
satisfies the given trait bounds for any given lifetime 'b
, which is exactly what HRTBs are for.
An alternative, less repetitive way of writing the trait bounds for the second implementation is this:
impl<'a, T: Clone> Mul<Complex<T>> for &'a Complex<T>
where
Self: for<'b> Mul<&'b Complex<T>, Output = Complex<T>>,
{
type Output = Complex<T>;
fn mul(self, rhs: Complex<T>) -> Complex<T> {
self.mul(&rhs)
}
}
Upvotes: 1
Reputation: 58805
The built-in numeric types implement these permutations using a macro. Doing it by hand, I'd start with the case where you are multiplying two values, rather than any references, and make sure that your Complex
struct is Copy
:
impl<T: Copy> Mul<Complex<T>> for Complex<T>
where
T: Add<T, Output = T>,
T: Sub<T, Output = T>,
T: Mul<T, Output = T>,
{
type Output = Complex<T>;
fn mul(self, rhs: Complex<T>) -> Complex<T> {
unimplemented!()
}
}
Note that you don't need any constraints on &T
- you can't return references to T
anyway, so you are going to have to copy them, which is why I've specified T: Copy
.
The rest of the implementations are now straightforward and can delegate to the simplest case:
impl<'a, T: Copy> Mul<Complex<T>> for &'a Complex<T>
where
T: Add<T, Output = T>,
T: Sub<T, Output = T>,
T: Mul<T, Output = T>,
{
type Output = Complex<T>;
fn mul(self, rhs: Complex<T>) -> Complex<T> {
(*self).mul(rhs)
}
}
impl<'a, T: Copy> Mul<&'a Complex<T>> for Complex<T>
where
T: Add<T, Output = T>,
T: Sub<T, Output = T>,
T: Mul<T, Output = T>,
{
type Output = Complex<T>;
fn mul(self, rhs: &'a Complex<T>) -> Complex<T> {
self.mul(*rhs)
}
}
impl<'a, 'b, T: Copy> Mul<&'a Complex<T>> for &'b Complex<T>
where
T: Add<T, Output = T>,
T: Sub<T, Output = T>,
T: Mul<T, Output = T>,
{
type Output = Complex<T>;
fn mul(self, rhs: &'a Complex<T>) -> Complex<T> {
(*self).mul(*rhs)
}
}
Upvotes: 0