Rust trait implementation with lifetimes

41 Views Asked by At

Some explanation as to what I am trying to do here. The full details of the calculation are not particularly important (and also cannot be made public). However, the following points should be noted: There are a set of operations which may be performed for any particular type T which is used in the calculation. T may be a floating point value, or an ndarray of floating point values:

  • float += float
  • float *= float
  • float + float
  • float * float
  • ndarray += float
  • ndarray += ndarray
  • ndarray *= float
  • ndarray + float
  • ndarray + ndarray
  • ndarray * float

To be more specific, ndarray does not support += unless the right hand side is a reference. So the += operators, should be AddAssign<&T> not AddAssign<T>.

I am trying to implement this as a trait for a particular type in Rust, but I cannot get the code to compile, because my expression of a reference lifetime is not valid. Because this trait takes a reference, the compiler encouraged me to introduce a lifetime. But I think I am associating that lifetime incorrectly.

Here is a link to the playground.

use ndarray; // 0.15.6

trait ExampleTrait {
    type Input;
    type Output;

    fn do_calculation(&mut self, input: Self::Input) -> Self::Output;
}

struct Transform<T> {
    current_state: Option<T>,
    scale_factor: f64,
}

impl<T> Transform<T> {
    fn new(scale_factor: f64) -> Transform<T> {
        Transform::<T> {
            current_state: None,
            scale_factor,
        }
    }
}

impl<'a, T> ExampleTrait for Transform<T>
where
    T: 'a
        + Clone
        + std::ops::AddAssign<f64>
        + std::ops::AddAssign<&'a T>
        + std::ops::MulAssign<f64>
        + std::ops::Add<T, Output = T>
        + std::ops::Add<f64, Output = T>
        + std::ops::Mul<f64, Output = T>,
{
    type Input = T;
    type Output = T;

    fn do_calculation(&mut self, input: Self::Input) -> Self::Output {
        let intermediate = input;
        let current_state = std::mem::take(&mut self.current_state);

        match current_state {
            Some(mut state) => {
                state *= self.scale_factor;
                state += &intermediate;
                let mut return_value = state.clone();
                return_value *= 1.0;
                return return_value;
            }
            None => {
                self.current_state = Some(intermediate.clone());
                return intermediate;
            }
        }
    }
}

fn main() {
    let tmp = 1.0;
    let input = ndarray::array![[tmp, tmp, tmp], [tmp, tmp, tmp]];

    let mut transform = Transform::<f64>::new(0.5);
    transform.do_calculation(50.0);

    let mut transform2 = Transform::<ndarray::Array2<f64>>::new(0.5);
    transform2.do_calculation(input.clone());
}

This is the error produced:

   Compiling playground v0.0.1 (/playground)
error[E0597]: `intermediate` does not live long enough
  --> src/main.rs:63:26
   |
37 | impl<'a, T> ExampleTrait for Transform<T>
   |      -- lifetime `'a` defined here
...
55 |         let intermediate = input;
   |             ------------ binding `intermediate` declared here
...
63 |                 state += &intermediate;
   |                 ---------^^^^^^^^^^^^^
   |                 |        |
   |                 |        borrowed value does not live long enough
   |                 requires that `intermediate` is borrowed for `'a`
...
75 |     }
   |     - `intermediate` dropped here while still borrowed

For more information about this error, try `rustc --explain E0597`.
error: could not compile `playground` (bin "playground") due to previous error

Btw I'm aware there's a bug due to not putting back the object taken by std::mem::take.

0

There are 0 best solutions below