0

I'm implementing basic operations for matrices on a generic field F. In doing so, I want to overload the add operator for the matrix type. The matrix type contains a Cow either owning or borrowing F. I want the resulting matrix sum to borrow the F of the added matrix regardless if it owns or borrows F.

The compiler complains that I return a reference created inside the function, but I don't understand why. It seems to me I'm referencing an input argument of the function and not data created locally. Here's my minimal example:

use std::{
    borrow::{Borrow, Cow},
    ops::Add,
};

pub trait Field {
    type FElt: Copy + Eq;

    fn zero(&self) -> Self::FElt;
    fn add(&self, a: Self::FElt, b: Self::FElt) -> Self::FElt;
}

pub struct Mat<'a, F: Clone + Eq + Field> {
    field: Cow<'a, F>,
    rows: usize,
    cols: usize,
    data: Vec<F::FElt>,
}

impl<'a, F: Clone + Eq + Field> Mat<'a, F> {
    pub fn zero(field: &'a F, rows: usize, cols: usize) -> Self {
        if rows == 0 || cols == 0 {
            panic!("Empty matrix");
        }

        Self {
            field: Cow::Borrowed(field),
            rows,
            cols,
            data: vec![field.zero(); rows * cols],
        }
    }
}

impl<'a, F: Clone + Eq + Field> Add for Mat<'a, F> {
    type Output = Self;

    fn add(self, other: Self) -> Self::Output {
        if self.field != other.field {
            panic!("Cannot add matrices: fields do not match");
        }
        if self.rows != other.rows || self.cols != other.cols {
            panic!("Cannot add matrices: dimensions do not match");
        }

        let f: &F = self.field.borrow();
        let mut sum = Mat::zero(f, self.rows, self.cols);
        for i in 0..self.rows * self.cols {
            sum.data[i] = f.add(self.data[i], other.data[i]);
        }
        sum
    }
}

fn main() {}

Here's the error I'm getting:

error[E0515]: cannot return value referencing local data `self.field`
  --> src/main.rs:51:9
   |
46 |         let f: &F = self.field.borrow();
   |                     ---------- `self.field` is borrowed here
...
51 |         sum
   |         ^^^ returns a value referencing data owned by the current function
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
gwafotapa
  • 3
  • 1
  • 1
  • 1
    I suspect you don't want a `Cow` for your field, but instead want an `Rc` (or more likely a `PhantomData`). – Jmb Apr 01 '20 at 13:14
  • I didn't know about `Rc`. This is exactly what I wanted, thank you! `PhantomData` would not work as I need to store the information about the field `F` (and I want to avoid repeating it in every element of the vector `data`). – gwafotapa Apr 04 '20 at 21:39

1 Answers1

1

It seems to me I'm referencing an input argument of the function and not data created locally.

self may not have been created locally, but it is moved into the add method (which takes self), so it will still be destroyed when the function returns. If the output were allowed to reference it, the reference would become dangling as soon as the function returned.

If you want the output to borrow from the input, you must take the input as a reference, which means implementing Add for &'b Mat<'a, F> instead of Mat<'a, F>. See How do I implement the Add trait for a reference to a struct? Note that the Output type must be Mat<'b, F> instead of Mat<'a, F>.

There's another, simpler solution for the problem as posed. Borrowing doesn't work because the Cow was mooved in to the function. Why not just move it back out? An easy way to do this is to modify self and return it rather than creating sum inside the function.

    fn add(mut self, other: Self) -> Self::Output {
        /* ... */

        for i in 0..self.rows * self.cols {
            self.data[i] = self.field.add(self.data[i], other.data[i]);
        }
        self
    }

Not only do you avoid a potentially expensive clone of field, but you also reuse data's buffer this way.

trent
  • 25,033
  • 7
  • 51
  • 90
  • 3
    "Borrowing doesn't work because the Cow was **mooved** in to the function." => I see what you did here... – Matthieu M. Apr 01 '20 at 13:47
  • Thank you for the answer. Implementing `Add` for a reference works but I want to implement `Add` for all four combinations of `Mat` and `&Mat` such that I can write a + b. But I guess all my trouble comes from the fact that I picked `Cow` where I should have picked `Rc` as Jmb suggested. – gwafotapa Apr 04 '20 at 21:49
  • Your simpler solution looks weird to me. As I understand it, if I write `a + b`, I will get the sum but matrix `a` will also mutate into the sum in the process which I don't want. – gwafotapa Apr 04 '20 at 22:00
  • @gwafotapa `a` is consumed by the expression `a + b`, so there's no way for the code to observe that. This is also how `String` implements `Add<&str>`, for example. – trent Apr 04 '20 at 22:06
  • @trentcl I hadn't thought about that. I understand your intent now. That's a great solution. Even though I'm now using `Rc` in my code, I can benefit from this and that's a good answer to my original problem. – gwafotapa Apr 05 '20 at 21:07