Newbie borrow checker fight - matching and mutating indexed content

I have a situation that, much simplified, looks something like this:

enum StaffObjectType
{
    DurationObject
    {
        whole_notes_from_staff_start: num_rational::BigRational
    },
    Clef
}

struct StaffObject
{
    distance_from_staff_start: i32,
    object_type: StaffObjectType
}

struct Staff
{
    contents: Vec<StaffObject>
}

impl Staff
{
    fn insert_object(&mut self, object: StaffObject, object_index: usize)
    {
        self.contents.insert(object_index, object);
        match self.contents[object_index].object_type
        {
            StaffObjectType::DurationObject{..} =>
            {
                for i in object_index + 1..self.contents.len()
                {
                    if let StaffObjectType::DurationObject{..} = self.contents[i].object_type
                    {
                        self.contents[i].distance_from_staff_start += 1;
                    }
                }
            },
            StaffObjectType::Clef => ()
        }
    }
}

I would like to add some code inside the DurationObject match arm in Staff.insert_object that reads from (but doesn't mutate, if that's relevant) the whole_notes_from_staff_start field of self.contents[object_index]. I'm having trouble figuring out how I can add that field in the pattern match that doesn't run afoul of the borrow checker.

In case a major redesign is necessary and it's helpful to know the goal of the code in order to comment on this: Staff.contents represents a sequence of characters arranged in a line. The spacing of these characters is specified by the StaffObject.distance_from_staff_start values. Staff.insert_object, after it inserts the object, changes the spacing of the objects already present as necessary to compensate for the insertion. This involves a lot of logic and field reads that I've left out - I didn't figure those specifics are relevant. The Clef variant of StaffObjectType is actually used for things in context too, including in its arm of the match in Staff.insert_object.

From the code you've shown, I would suggest inserting the new StaffObject not at the beginning, but at the end. Then you can freely inspect/mutate it independent of the other objects in self.contents.

If that is not possible, have a look at the split_at and split_at_mut methods of slices, and the splitmut crate for more related APIs.

The immediate problems I'm having happen even if I never insert the new object at all, though there might indeed be another round of problems later that have to do with when the object is inserted. The first thing I did was change the match pattern from

StaffObjectType::DurationObject{..}

to

StaffObjectType::DurationObject{whole_notes_from_staff_start}

It makes sense to me that this would give me "cannot move out of indexed content," since num_rational::BigRational doesn't implement copy. So then I tried changing it to

StaffObjectType::DurationObject{ref whole_notes_from_staff_start}

The error then becomes that self.contents is borrowed immutably at the top of the match, then again mutably on the line where I attempt to increment one of its elements. I don't know where to go from there since, if indexing a vec itself involves a borrow, it seems that no matter what I do, I'm going to have two borrows, at least one of which is mutable.

But the object you're matching initially is the new object, no? At least this looks like it:

        self.contents.insert(object_index, object);
        match self.contents[object_index].object_type {

So if you don't insert it, you won't borrow self.contents there.

Oh, haha. I tried something like this earlier - changing

self.contents.insert(object_index, object);
match self.contents[object_index].object_type

to

match object.object_type

but got caught up on errors that, I just realized, were due to forgetting to change "object" to "mut object" in the function signature.

Not being able to insert the object until the end does complicate the logic annoyingly, but I don't see any insurmountable difficulties left at the moment. I'll see if I can get it all to work.

OK, nice. From the code you've shown it was the easiest route; with more logic it may not be.

@AlexanderKindel, the example you gave seems super amenable to split_at_mut, as @birkenfeld suggested. That should allow you to not have to make concessions, such as inserting the object only at the end. I imagine it can look something like:

self.contents.insert(object_index, object);
let (first, rest) = self.contents.split_at_mut(object_index + 1);

match first.last().unwrap().object_type {
	StaffObjectType::DurationObject { ref whole_notes_from_staff_start } => {
		for t in rest {
			if let StaffObjectType::DurationObject { .. } = t.object_type {
				t.distance_from_staff_start += 1;
			}
		}
	}
	StaffObjectType::Clef => (),
}

Have you tried changing it to mut ref?

StaffObjectType::DurationObject { mut ref whole_notes_from_staff_start }

Is mut ref different than ref mut? The former doesn't seem to be valid syntax. Only changing to ref mut has the effect that every time I call self.contents.len() or index out of self.contents inside an arm, I get an error that I can't immutably borrow self.contents because it has already been mutably borrowed at the top of the match.

The reason this helps is because the match condition refers to one slice while the mutations in the arm refer solely to the other, right? I was thinking that wouldn't be enough because in practice, I have to be able to mutate a selection of elements that the new object might fall in the middle of, but now that I look again, that case only happens in the Clef branch, which doesn't need to refer to any fields in its match pattern, while the DurationObject branch should work with split_at_mut. Maybe I could handle the DurationObject case in an if let and return at the end, leaving the rest of the function body to handle the clef case in a different way.

Right - it splits the vec (or rather, the slice the Vec can deref to) into disjoint halves, and Rust allows holding mutable references to disjoint data.

You can likely rejig the code around as you see fit, but split_at_mut is a useful tool to know about in general.