Home > Software engineering >  Rust "..does not live long enough"
Rust "..does not live long enough"

Time:08-24

I am having difficulty bounding the add_a_to_b function. I have tried to bound it many different ways, and the only one I made it to compile was the following.

use std::ops::{Add, AddAssign};


trait TheMatrix {}


#[derive(Clone, Debug)]
struct Nd<T> {
    data: Vec<T>
}


impl <T> TheMatrix for Nd<T> {}


impl <'a, 'b, T> Add<&'b Nd<T>> for &'a Nd<T>
    where
        T: AddAssign   Clone   Copy,
{
    type Output = Nd<T>;
    
    fn add(self, rhs: &'b Nd<T>) -> Self::Output {
        let mut new_nd = self.clone();
        
        new_nd.data.iter_mut()
            .zip(rhs.data.iter())
            .for_each(|(lf, rh)| *lf  = *rh);
        
        new_nd
    }
}


fn add_a_to_b<'a, 'b, M>(a: &'a M, b: &'b M, c: &mut M)
    where
        &'a M: Add<&'b M, Output = M>,
{
    let d = a   b;
    let e = a   b;
    *c = &e   &d;
}


fn main() {
    let a = Nd { data: vec![1.0, 2.0, 3.0] };
    let b = Nd { data: vec![2.0, 3.0, 4.0] };
    let mut c = Nd { data: vec![0.0; 3] };

    add_a_to_b(&a, &b, &mut c);
    
    println!("c = {:?}", c);
}

One of the ways I tried was (because a preview question),

M: for <'i> Add<&'i M>

but it didn't work, can't exactly understand why!

Now I have no compile error related to the bounding, but I have difficulties in performing the operations inside the function raising the error of does not live long enough for both d and e. I even tried to initialize first d and e by cloning a (let d = a.clone()), but also without success. I am not sure if this is due to the way bounding is performed or to my misunderstanding of borrowing.

Furthermore, the way code is implemented, can a and b have different types (assuming that there is implemented the add trait for each combination and also implement TheMatrix trait)? How should it be done in case it doesn't deal with it the way it is?

CodePudding user response:

First things first

This

impl<'a, 'b, T> Add<&'b Nd<T>> for &'a Nd<T>
    where
        T: AddAssign   Clone   Copy,
{
    type Output = Nd<T>;
    
    fn add(self, rhs: &'b Nd<T>) -> Self::Output {
    ...

can be simplified to this

impl<'a, T> Add<&'a Nd<T>> for &'a Nd<T>
    where
        T: AddAssign   Clone   Copy,
{
    type Output = Nd<T>;
    
    fn add(self, rhs: &'a Nd<T>) -> Self::Output {
    ...

with no problem. Now we're just using one lifetime. This is basically saying as long as both self and rhs have an intersecting lifetime during the call of add, you can add them. And this can be even further simplified to

impl<T> Add<&Nd<T>> for &Nd<T>
    where
        T: AddAssign   Clone   Copy,
{
    type Output = Nd<T>;
    
    fn add(self, rhs: &Nd<T>) -> Self::Output {
    ...

where we drop the lifetime annotation altogether. Rust infers what we mean.

The problem with add_a_to_b

Again, we can simplify the signature to just one lifetime:

fn add_a_to_b<'a, M>(a: &'a M, b: &'a M, c: &mut M)
    where
        &'a M: Add<&'a M, Output = M>,

This still won't compile. This is because the compiler only knows what we told it. And we've told it that a and b have type &'a M and that &'a M can be added to &'a M. In the body of the function we then do this:

let d = a   b;
let e = a   b;
*c = &d   &e;

This tells the compiler a few more things: d and e have type M, and &d &e needs to produce an M as well because that's the type of *c.

But the compiler only knows that &M &M can make an M if both of those references live for 'a. But 'a is the lifetime of the arguments we passed in, so 'a has to outlive the function. But d and e get dropped at the end of the function so no reference to them can life for that long.

To fix this, we need to tell the compiler that actually any &M &M can make another M. And in order to do that we need to use HRTBs:

fn add_a_to_b<'a, M>(a: &'a M, b: &'a M, c: &mut M)
    where
        for<'m> &'m M: Add<&'m M, Output = M>,

The for<'m> &'m M: Add<Output = M> tells the compiler that for any lifetime 'm I can add two references to an M to get another M.

Now we don't even need the lifetime 'a anymore:

fn add_a_to_b<M>(a: &M, b: &M, c: &mut M)
    where
        for<'m> &'m M: Add<&'m M, Output = M>,

More generics

Now as for the second question: No, a and b can't have different types, they both have to be &M where M is the same across all three arguments. You can make it more generic though, by telling the function to accept arguments of any type as long as they can be added together to get Ms:

fn add_a_to_b<S1, S2, M>(a: &S1, b: &S2, c: &mut M)
    where
        for<'s> &'s S1: Add<&'s S2, Output = M>,
        for<'m> &'m M: Add<&'m M, Output = M>,
    ...

This will work without any changes to the body of the function.

  • Related