3

I'm learning Rust and I'm fighting against the borrow checker.

I have a basic Point structure. I have a scale function that modifies all the coordinates of the point. I would like to call this method from another method named convert:

struct AngleUnit;

struct Point {
    x: f32,
    y: f32,
    z: f32,
    unit: AngleUnit,
}

fn factor(_from: AngleUnit, _to: AngleUnit) -> f32 {
    1.0
}

impl Point {
    pub fn new(x: f32, y: f32, z: f32, unit: AngleUnit) -> Point {
        Point { x, y, z, unit }
    }

    fn scale(&mut self, factor: f32) {
        self.x *= factor;
        self.y *= factor;
        self.z *= factor;
    }

    fn convert(&mut self, unit: AngleUnit) {
        let point_unit = self.unit;
        self.scale(factor(point_unit, unit));
    }
}

Now I have the following error:

cannot move out of borrowed content

What am I doing wrong?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
yageek
  • 4,115
  • 3
  • 30
  • 48

3 Answers3

9

The complete error message states:

error[E0507]: cannot move out of borrowed content
  --> src/lib.rs:26:26
   |
26 |         let point_unit = self.unit;
   |                          ^^^^^^^^^
   |                          |
   |                          cannot move out of borrowed content
   |                          help: consider borrowing here: `&self.unit`

Which is useful to understand where the move is occurring.

The simplest solution is to implement either Copy or Clone for your AngleUnit type. If it's Copy, your code will work as-is. If it's only Clone, you have to explicitly call .clone() to make a duplicate.

If your type cannot be made Copy, then you can use references, as the compiler suggests:

fn factor(_from: &AngleUnit, _to: &AngleUnit) -> f32 {
    1.0
}
fn convert(&mut self, unit: AngleUnit) {
    let point_unit = &self.unit;
    self.scale(factor(point_unit, &unit));
}

The original problem all boils down to this line:

let point_unit = self.unit;

What should the value of point_unit be?

If we moved the value from self.unit to point_unit, then what would the value of self.unit be? The "easy" solution would be that it is undefined memory, but experience has shown that we programmers will screw that up and introduce exciting-to-debug problems.

We could copy the value automatically, but what would happen if AngleUnit were a type that took up 10 MiB of space? Then an innocent looking line just sucked up a bunch of memory and time. That's not very nice either.

Instead, Rust makes it so that types are moved by default and you cannot leave an object in an undefined state. Certain types can opt into the ability to be automatically copied — this is the Copy trait. You can also allow types to be explicitly copied — the Clone trait. You can also obtain a reference to an existing value and pass that around. The borrow checker will prevent you from using that reference after it is no longer valid.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Nice answer; however, visitors from google need to read a lot, although the solution is `#[derive(Clone, Copy)]` in many cases. Maybe emphasize your "The simplest solution is..." section :) – Lukas Kalbertodt Jan 06 '16 at 07:24
4

You need to mark the AngleUnit with the #[derive(Copy, Clone)] attribute in order to allow Rust to copy the AngleUnit from one variable to another, like you do in let point_unit = self.unit.

By default, Rust is only allowed to move your structures since moving is generally more efficient than making a copy. For instance, if your structure would have had a Vec or a String in it then to copy the structure the program would have to allocate new memory for Vec or String contents on the heap. Whereas moving a Vec or a String incurs no extra heap allocations.

In this case I guess the AngleUnit is just a wrapper around a small primitive value so it's completely okay to copy it.

Here's an example of your code with the attribute added:

#[derive(Copy, Clone)]
struct AngleUnit;

struct Point {
    x: f32,
    y: f32,
    z: f32,
    unit: AngleUnit,
}

fn factor(from: AngleUnit, to: AngleUnit) -> f32 {
    1f32
}

impl Point {
    pub fn new(x: f32, y: f32, z: f32, unit: AngleUnit) -> Point {
        Point { x, y, z, unit }
    }

    fn scale(&mut self, factor: f32) {
        self.x *= factor;
        self.y *= factor;
        self.z *= factor;
    }

    fn convert(&mut self, unit: AngleUnit) {
        let point_unit = self.unit;
        self.scale(factor(point_unit, unit));
    }
}

fn main() {}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
ArtemGr
  • 11,684
  • 3
  • 52
  • 85
2

You are moving self.unit at:

let point_unit = self.unit;

Try using references or making the type Copy.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Cecilio Pardo
  • 1,717
  • 10
  • 13