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
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
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.
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.
@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 => (),
}
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.