Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you implement math ops on structs without explicit references or ownership moving?

I can't work out how to have clean looking maths on structs without requiring that those struct values be copied everywhere.

If you wanted to have a struct that you could perform math on, you'd write something like this:

use std::ops::*;

struct Num {
    i: i32,
}

impl Add for Num {
    type Output = Num;
    fn add(self, other: Num) -> Num {
        Num {
            i: self.i + other.i,
        }
    }
}

(This is a simplified example. An actual example might be doing Vector maths)

This lets us write nice a + (b / (c * d)) style code.

Due to borrowing semantics, the above code falls over as quickly as a + b + a. Once a is used once it can't be used again, as ownership was moved to the relevant function (i.e. add).

The simple way of solving this is to implement Copy for the struct:

#[derive(Copy)]
struct Num {
    i: i32,
}

This means when Nums are passed to add, their values are automatically cloned so that they can be dropped cleanly.

But this seems inefficient! We don't need these structs to be duplicated all over the place: it's read-only, and we really just need it to be referenced to create the new structure we're returning.

This leads me to think that we should implement the math operations on references instead:

impl<'a> Add for &'a Num {
    type Output = Num;
    fn add(&'a self, other: &'a Num) -> Num {
        Num {
            i: self.i + other.i,
        }
    }
}

Now we have math operations where we aren't cloning data all over the place, but now our math looks gross! a + (b / (c * d)) now must be &a + &(&b / &(&c * &d)). It doesn't help if you have your value type references either (eg let a = &Num { /* ... */ }), because the return value of add is still a Num.

Is there a clean way to implement ops for structs such that math operations look clean, and the struct values aren't being copied everywhere?

Related:

  • Generic math without copying in rust
like image 674
SCdF Avatar asked Mar 01 '18 22:03

SCdF


1 Answers

No; the traits consume by value, there's no way around that.

But this seems inefficient! We don't need these structs to be duplicated all over the place: it's read-only, and we really just need it to be referenced to create the new structure we're returning.

I wouldn't worry about the efficiency of copying a single integer. That's what computers do. In fact, a reference would likely be slower as a reference is also basically an integer which has to be copied and then a piece of memory has to be looked up, also copying the referred-to integer into registers.

I'm obvious[ly] not copying a single integer!

An actual example might be doing Vector maths

Then the problem becomes one of confusing your users. Without looking at the implementation, how could a user know that a + a is "lightweight" or not. If you, the implementer of your type, knows that it's lightweight to copy, you mark it Copy. If it's not, then references need to be made.


That's the situation today. There is some experimental work that might indeed make this a little bit nicer in the future:

imagine never having to write [...] let z = &u * &(&(&u.square() + &(&A * &u)) + &one); again

This experiment spawned from a now-deferred RFC.

As an amusing aside, this ugly syntax is referred to as the Eye of Sauron.

like image 138
Shepmaster Avatar answered Nov 06 '22 08:11

Shepmaster