[SOLVED] Avoid duplication for &mut vs & access

Hello humans. I'm writing up a simple labyrinth struct, only allowing vertical and horizontal walls. I have two very similar methods get_wall and get_wall_mut (code is below). Do you have any suggestions for how to reduce code duplication here? Each tile in the vector has the wall information ("wallyness") for its east and south wall.

pub fn get_wall(&self, point: &Vec2, direction: Direction) -> Wallyness {
	let (x, y) = point.to_tuple();
	// Normalize direction by a single recursion step.
	use Direction::*;
	match direction {
		North => return self.get_wall(&Vec2::new(x, y-1), South),
		West => return self.get_wall(&Vec2::new(x-1, y), East),
		_ => ()
	}
	// At this point, the direction is either South or East.
	if let Some(i) = self.point_to_index(point) {
		if x == (self.width - 1) as i32 && direction == East {
			return Wallyness::Wall;
		}
		if y == (self.height - 1) as i32 && direction == South {
			return Wallyness::Wall;
		}
		let t = &self.data[i];
		match direction {
			East => t.east,
			South => t.south,
			_ => panic!("illegal state"),
		}
	} else {
		Wallyness::Wall
	}
}
pub fn get_wall_mut(&mut self, point: &Vec2, direction: Direction) -> Option<&mut Wallyness> {
	let (x, y) = point.to_tuple();
	// Normalize direction by a single recursion step.
	use Direction::*;
	match direction {
		North => return self.get_wall_mut(&Vec2::new(x, y-1), South),
		West => return self.get_wall_mut(&Vec2::new(x-1, y), East),
		_ => ()
	}
	// At this point, the direction is either South or East.
	if let Some(i) = self.point_to_index(point) {
		if x == (self.width - 1) as i32 && direction == East {
			return None;
		}
		if y == (self.height - 1) as i32 && direction == South {
			return None;
		}
		let t = &mut self.data[i];
		match direction {
			East => Some(&mut t.east),
			South => Some(&mut t.south),
			_ => panic!("illegal state"),
		}
	} else {
		None
	}
}
1 Like

I don’t think there’s any good way to abstract this other than a macro.

1 Like

Thank you for that idea. I didn't even consider macros. It should probably take as argument an expression to use with the found "wallyness" and one expression to use for the None/default value case.

I'm close. The only missing piece for the unification is now how to declare a function once with &self argument and once with &mut self argument. I'll make a separate thread for that because it's a different question.

UPDATE: The follow-up thread is here: [SOLVED] Macros: &mut self vs. &self

Following is the code I have so far. Note that I still need two separate macros, because that one piece is missing for unification.

macro_rules! mut_ref {
  ( $x:expr ) => {
    &mut $x
  }
}

macro_rules! some_mut {
  ( $x:expr ) => {
    Some(&mut $x)
  }
}

macro_rules! labyrinth_access_mut {
  ( $funcname:ident, $rett:ty, $ref_op:ident, $succ:ident, $nosucc:expr ) => {
    pub fn $funcname (&mut self, point: &Vec2, direction: Direction) -> $rett {
      let (x, y) = point.to_tuple();
      // Normalize direction by a single recursion step.
      use Direction::*;
      match direction {
        North => return self.$funcname(&Vec2::new(x, y-1), South),
        West => return self.$funcname(&Vec2::new(x-1, y), East),
        _ => ()
      }
      // At this point, the direction is either South or East.
      if let Some(i) = self.point_to_index(point) {
        if x == (self.width - 1) as i32 && direction == East {
          return $nosucc;
        }
        if y == (self.height - 1) as i32 && direction == South {
          return $nosucc;
        }
        let t = $ref_op!(self.data[i]);
        match direction {
          East => $succ!(t.east),
          South => $succ!(t.south),
          _ => panic!("illegal state"),
        }
      } else {
        $nosucc
      }
    }
  }
}

macro_rules! nomut_ref {
  ( $x:expr ) => {
    & $x
  }
}

macro_rules! expr_id {
  ( $x:expr ) => {
    $x
  }
}

macro_rules! labyrinth_access_nomut {
  ( $funcname:ident, $rett:ty, $ref_op:ident, $succ:ident, $nosucc:expr ) => {
    pub fn $funcname (&self, point: &Vec2, direction: Direction) -> $rett {
      let (x, y) = point.to_tuple();
      // Normalize direction by a single recursion step.
      use Direction::*;
      match direction {
        North => return self.$funcname(&Vec2::new(x, y-1), South),
        West => return self.$funcname(&Vec2::new(x-1, y), East),
        _ => ()
      }
      // At this point, the direction is either South or East.
      if let Some(i) = self.point_to_index(point) {
        if x == (self.width - 1) as i32 && direction == East {
          return $nosucc;
        }
        if y == (self.height - 1) as i32 && direction == South {
          return $nosucc;
        }
        let t = $ref_op!(self.data[i]);
        match direction {
          East => $succ!(t.east),
          South => $succ!(t.south),
          _ => panic!("illegal state"),
        }
      } else {
        $nosucc
      }
    }
  }
}

and in the struct:

labyrinth_access_mut!(get_wall_mut, Option<&mut Wallyness>, mut_ref, some_mut, None);
labyrinth_access_nomut!(get_wall, Wallyness, nomut_ref, expr_id, Wallyness::Wall);