2

I'm writing a ray-tracer and I want to be able to subtract my 3D vectors:

use std::ops::Sub;

#[derive(Clone, Debug)]
pub struct Vec3 {
    pub v: [f64; 3],
}

impl Sub for Vec3 {
    type Output = Vec3;

    fn sub(self, other: Vec3) -> Vec3 {
        Vec3 {
            v: [
                self.v[0] - other.v[0],
                self.v[1] - other.v[1],
                self.v[2] - other.v[2],
            ],
        }
    }
}

This seems to work. However, when I try to use it:

fn main() {
    let x = Vec3 { v: [0., 0., 0.] };
    let y = Vec3 { v: [0., 0., 0.] };
    let a = x - y;
    let b = x - y;
}

I get complaints from the compiler:

error[E0382]: use of moved value: `x`
  --> src/main.rs:26:13
   |
25 |     let a = x - y;
   |             - value moved here
26 |     let b = x - y;
   |             ^ value used here after move
   |
   = note: move occurs because `x` has type `Vec3`, which does not implement the `Copy` trait

error[E0382]: use of moved value: `y`
  --> src/main.rs:26:17
   |
25 |     let a = x - y;
   |                 - value moved here
26 |     let b = x - y;
   |                 ^ value used here after move
   |
   = note: move occurs because `y` has type `Vec3`, which does not implement the `Copy` trait

How can I write the subtraction operator so that the code above works?

Please don't tell me I should an existing 3D math module. I'm sure there's something better, but I'm after learning how to do it myself to learn the language.

How do I implement the Add trait for a reference to a struct? doesn't help as it requires specifying lifetimes for object which I'm not at yet.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Jeffrey
  • 11,063
  • 1
  • 21
  • 42
  • 1
    Possible duplicate of [How do I implement the Add trait for a reference to a struct?](https://stackoverflow.com/questions/28005134/how-do-i-implement-the-add-trait-for-a-reference-to-a-struct) – mcarton Aug 14 '18 at 15:28
  • @trentcl thanks!! Consider making this an answer. I'm still processing the other options, but since I don't know yet what a lifetime is, I'm having difficulty comparing them. Your suggestion fixes my issue, but I'm not sure the copy is the correct trade-off. – Jeffrey Aug 14 '18 at 15:38
  • @Jeffrey The `Copy` marker permits to silently copy your struct when needed. – Boiethios Aug 14 '18 at 15:44
  • I don't think the last paragraph is a legitimate constraint to put on a question. Would you ask your driving teacher "How do I parallel park? Oh, but I can't turn the wheel, because I'm not at *steering* yet." Fortunately, in this case lifetimes are not required (witness my answer), and the linked question does not address the question as posed anyway (you would have to use `&x - &y` instead of `x - y`). – trent Aug 15 '18 at 13:33

1 Answers1

6

In the example, the compiler tells you why x has been invalidated by the move:

   = note: move occurs because `x` has type `Vec3`, which does not implement the `Copy` trait

In this case, you can simply add #[derive(Copy)] to give Vec3 copy semantics:

#[derive(Clone, Copy, Debug)]
pub struct Vec3 {
    pub v: [f64; 3],
}

Copy is a marker trait that indicates to the compiler that values of a type do not become invalid when they are moved from. A type with this property is said to have copy semantics, while a type that does not implement Copy is said to have move semantics. Is it possible to make a type only movable and not copyable? and How does Rust provide move semantics? explain this concept in more detail.


However, you can only implement Copy for types that contain only other Copy types. If Vec3 actually held a Vec inside it, the compiler would not let you implement Copy for it. Fortunately, references do implement Copy, so you can instead implement Sub for a reference to Vec3, using the approach described in How do I implement the Add trait for a reference to a struct?

trent
  • 25,033
  • 7
  • 51
  • 90