How to work around partial mutable borrow?

Hi, kind of a beginner question here. I'm trying to write a function that looks like this:

fn apply_jump_request(game: &mut GameState) -> {
    if let Some(raid) = &mut {
        let position = queries::compute_encounter(game)
        raid.encounter = Some(position);

fn compute_encounter(game: &GameState) -> u32 { ... }

But the borrow checker won't let me do this, I can't invoke compute_position() while I have a mutable borrow open. Assuming I don't want to move this computation outside of the if branch, what is the best design option here? Should I just do a non-mutable borrow and then = Some(position)

since I know that cannot possibly contain None, even though the borrow checker can't prove this?

Since is already an Option, you could use take() to move it out and then put it back in after the call to compute_encounter(). This assumes you don't need raid inside compute_encounter(); if you do, then you will need to pass it in separately.

1 Like

Maybe something like this?

fn apply_jump_request(game: &mut GameState) {
    if {
        let position = compute_encounter(game); = Some(Raid { encounter: Some(position) });

Edit: Sorry, this is probably useless since it assumes that encounter is the only field of the Raid.

Other options are:

  • split GameState into smaller structs, so that you can call functions on disjoint fields queries::compute_encounter(&game.only_compute_part).

  • wrap raid field in RefCell or Mutex, which will let you borrow it while using &GameState elsewhere.

  • defer mutation until next frame. Bigger game engines often avoid modifying game state during a frame, and have a queue of actions to perform between frames. queue.push(SetRaidPosition(position)).


Here's an article about the situation which walks through examples of some of the workarounds mentioned above.


this kind of problem generally indicates design choices that are not well suited for rust programming paradigms. most such problems come from the habit of putting too many states into a single struct and trying to implement all operations as methods on the that struct. within such methods, all the states are borrowed because of self, so you'll have to find a way to circumvent the borrow checker, either to prefer performance but risk safety (e.g. MaybeUninit, UnsafeCell), or pay a little runtime overhead to use the safe Option, RefCell, Cell, etc.

if you have type names containing words like Application, Engine, Manager etc, be extra careful not to fall into what I call the "borrow whole program" situation.

as one gains more experience in rust, it is not too hard to adapt to design patterns that are more fitting for rust, but for existing codebase, it's really hard to move away from legacy architectural limitations. if the solutions proposed above won't fit your problem, there's the partial-borrow crate. it's the sledgehammer if you think your problems are all nails.

it use procedural macro to generate projected proxy types which you can borrow individual fields with different modes. using OP's problem as example:

struct GameState {
	raid: Option<u32>,
	field_a: i32,
	field_b: i32,

/// this function requires exclusive access to the whole GameState
fn apply_jump_request(game: &mut GameState) {
	// split GameState into `part`1 and `part2`
	// we specify `part1` mut borrows a single `raid` field
	// part2 will have calculated type with the "left over" fields and mutability
	let (part1, part2) =
		SplitOff::<partial!(GameState mut raid, !*)>::split_off_mut(game);
	if let Some(raid) = {
		// compute_encounter need const borrows excluding `raid`
		// we have part2 with mut borrows, which satisfy the type requirement
		let position = compute_encounter(part2.as_ref());

/// this function don't need the full GameState,
/// we explicitly excluded the `raid` field
/// other fields need immutable borrow (note the `const` keyword)
fn compute_encounter(game: &partial!(GameState ! raid, const *)) -> u32 {
1 Like

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.