3

I'm having a lot of fun trying to solve the robot simulator Exercism exercise, but I'm facing a value moving problem for which I don't seem to be able to come up with an elegant solution:

impl Robot {
    pub fn new(x: isize, y: isize, d: Direction) -> Self {
        Robot { position: Coordinate { x: x, y: y }, direction: d }
    }

    pub fn turn_right(mut self) -> Self {
        match self.direction {
           // ...
        };
        self
    }

    pub fn turn_left(mut self) -> Self {
        match self.direction {
           // ...
        };
        self
    }

    pub fn advance(mut self) -> Self {
        match self.direction {
           // ...
        };
        self
    }

    pub fn instructions(self, instructions: &str) -> Self {
        for instruction in instructions.chars() {
            match instruction {
                'A' => { self.advance(); },
                'R' => { self.turn_right(); },
                'L' => { self.turn_left(); },
                _   => {
                    println!("{} is not a valid instruction", instruction);
                },
            };
        }
        self
    }

I get this error :

enter code hereerror[E0382]: use of moved value: `self`
  --> src/lib.rs:60:26
   |
60 |                 'A' => { self.advance(); },
   |                          ^^^^ value moved here in previous iteration of loop
   |
   = note: move occurs because `self` has type `Robot`, which does not implement the `Copy` trait

error[E0382]: use of moved value: `self`
  --> src/lib.rs:61:26
   |
60 |                 'A' => { self.advance(); },
   |                          ---- value moved here
61 |                 'R' => { self.turn_right(); },
   |                          ^^^^ value used here after move
   |
   = note: move occurs because `self` has type `Robot`, which does not implement the `Copy` trait

I think I get the error because advance() returns self, but I don't understand why the value is still moved as it's used inside a block. Do I really have to implement Copy or am I missing a lifetime use case?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
stamm
  • 129
  • 1
  • 10
  • 1
    Could you do it with borrowing? Also, why not implement `Copy`? – Eli Sadoff Jan 15 '17 at 17:31
  • 1
    Don't implement `Copy`, but read about [the builder pattern](https://aturon.github.io/ownership/builders.html) – wimh Jan 15 '17 at 17:37
  • @EliSadoff I'm actually trying to learn how to write good code. I think copying here would be bad because it would take resources unnecessarily. – stamm Jan 15 '17 at 20:18
  • @wimh: the code construction look alike the one I was trying to build, but I didn't find my answer in it. Thank you for the link, this pages seems full of great things by the way. – stamm Jan 15 '17 at 20:20

2 Answers2

7

I think I get the error because advance() returns self ?

No, you're getting that error because advance consumes self (and your other methods do, too).

The idiomatic solution to your problem is almost certainly to have your methods take a mutable reference (&mut) to self instead of taking self by value. E.g. the signature pub fn turn_right(mut self) -> Self would become pub fn turn_right(&mut self) (note that the latter does not return anything). You can manipulate the robot's state through the reference, and your instructions function should work fine.

If for some reason you want to continue to have the methods take self by value, you could rewrite instructions as follows:

pub fn instructions(self, instructions: &str) -> Self {
    let mut robot = self;
    for instruction in instructions.chars() {
        robot = match instruction {
            'A' => { robot.advance() },
            'R' => { robot.turn_right() },
            'L' => { robot.turn_left() },
            _   => {
                println!("{} is not a valid instruction", instruction);
                robot
            },
        };
    }
    robot
}

I.e. continue passing the robot's state by value, but make sure that the new state is bound to a variable at every loop iteration. (I've not tried to compile this code, but the principle should be sound.)

fjh
  • 12,121
  • 4
  • 46
  • 46
  • Thank you, your second solution actually worked. I did try to use references but I had the same problem because the test suite uses chaining calls. If I understand well, the fact that the returned `self` from advance for example, was moving the value, but it was never moved back to the `fn instructions` context ? – stamm Jan 15 '17 at 20:04
  • Looking at the other exercism users, you can actually do it with fold : instructions.chars().fold(self, |robot, c| { match c { 'L' => robot.turn_left(), 'R' => robot.turn_right(), 'A' => robot.advance(), _ => panic!("unexpected char") } }) – stamm Jan 15 '17 at 20:26
  • 1
    @stamm Yes, that's exactly the problem. Once you pass a value to a method by value, you can no longer use it. But since your methods return the changed state, you can continue using that if you assign the return value to a variable. – fjh Jan 15 '17 at 21:15
3

Looking at the other users answers, you can actually do it with fold :

pub fn instructions(self, instructions: &str) -> Self {
    instructions.chars().fold(self, |robot, c| {
        match c {
            'L' => robot.turn_left(),
            'R' => robot.turn_right(),
            'A' => robot.advance(),
            _ => panic!("unexpected char")
        }
    })
}

It seems to keep moving back robot into the scope.

stamm
  • 129
  • 1
  • 10