Macro version of `let` that does copy & paste?

I have a boring version of Pacman in C that I'm adapting into Rust to learn about Rust. I have this large structure that contains information about the actors and their graphics.

I have a situation where I want to use a short-hand for two different pieces of the data-structure, spr and actor. But I get errors because self is borrowed multiple times. The code is here and doesn't work:

    // update Pacman sprite
    {
      let spr = self.spr_pacman();
      if spr.enabled {
        let actor = &self.game.pacman.actor;
        spr.pos = actor.pos.actor_to_sprite_pos();
        if (self.game.freeze & FreezeType::EatGhost as u8) != 0 {
          // hide Pacman shortly after he's eaten a ghost (via an invisible Sprite tile)
          spr.tile = SPRITETILE_INVISIBLE;
        } else if (self.game.freeze & (FreezeType::Prelude as u8 | FreezeType::Ready as u8)) != 0 {
          // special case game frozen at start of round, show Pacman with 'closed mouth'
          spr.tile = SPRITETILE_PACMAN_CLOSED_MOUTH;
        } else if (self.game.freeze & FreezeType::Dead as u8) != 0 {
          // play the Pacman-death-animation after a short pause
          if self
            .timing
            .after(self.game.pacman_eaten, PACMAN_EATEN_TICKS as u32)
          {
            self.spr_anim_pacman_death(
              self.timing.since(self.game.pacman_eaten) - PACMAN_EATEN_TICKS as u32,
            );
          }
        } else {
          // regular Pacman animation
          self.spr_anim_pacman(actor.dir, actor.anim_tick);
        }
      }
    }

If I just copy and past the definition of spr and actor into every one of their uses the code DOES work:

    // update Pacman sprite
    {
      if self.spr_pacman().enabled {
        self.spr_pacman().pos = self.game.pacman.actor.pos.actor_to_sprite_pos();
        if (self.game.freeze & FreezeType::EatGhost as u8) != 0 {
          // hide Pacman shortly after he's eaten a ghost (via an invisible Sprite tile)
          self.spr_pacman().tile = SPRITETILE_INVISIBLE;
        } else if (self.game.freeze & (FreezeType::Prelude as u8 | FreezeType::Ready as u8)) != 0 {
          // special case game frozen at start of round, show Pacman with 'closed mouth'
          self.spr_pacman().tile = SPRITETILE_PACMAN_CLOSED_MOUTH;
        } else if (self.game.freeze & FreezeType::Dead as u8) != 0 {
          // play the Pacman-death-animation after a short pause
          if self
            .timing
            .after(self.game.pacman_eaten, PACMAN_EATEN_TICKS as u32)
          {
            self.spr_anim_pacman_death(
              self.timing.since(self.game.pacman_eaten) - PACMAN_EATEN_TICKS as u32,
            );
          }
        } else {
          // regular Pacman animation
          self.spr_anim_pacman(self.game.pacman.actor.dir, self.game.pacman.actor.anim_tick);
        }
      }
    }

I understand why the original version is dangerous, because I could send the two references to different places and it would be very bad. In this circumstance, though, it is safe.

Is there a "call-by-name" version of let that does a "copy & paste" like this? I feel like this would be a common macro but I don't know enough about the Rust world to know what to search for to find it.

Part of the problem here is that you are using a function to do the borrow. The borrow checker never looks in the body of a function you're calling, so it doesn't know that spr_pacman() doesn't conflict with other borrows.

It's not clear from the amount of code that you've provided what exactly spr_pacman() is doing — is it just returning &mut self.game.pacman? If so, then the idiomatic solution is to do that in your code.

let spr = &mut self.game.pacman;
...
spr.pos = spr.actor.pos.actor_to_sprite_pos();
// and so on

As long as you use field accesses, not functions, the compiler can observe that they don't conflict with each other.

2 Likes

Here's an article on the problem more generally.

1 Like

If no better solution exists, the rust approach to avoiding literal copy-and-paste is of course macros, which can even mention local variables like self when defined inside of a function or block, e. g.

macro_rules! spr {
    () => { self.spr_pacman() }
}
macro_rules! actor {
    () => { &self.game.pacman.actor }
}
if spr.enabled {
   … 
   …
} 

(untested)

would allow you to use spr!() and actor!().

1 Like

These are all very helpful, thank you so much!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.