I have a function that looks something like this (I cut out some irrelevant parts). It’s doing a mutable borrow inside a loop.
fn touches_began(&mut self, event: UIEvent) {
let mut wctx = WindowCtx::new(&mut self.ctx_state);
let te = event.touches_enumerator();
while let Some(obj) = te.next_object() {
let pointer_id: u64 = ...;
let mut pevent = PointerEvent::new(pointer_id, &mut wctx);
pointer_down(&mut self.state, &mut pevent);
}
self.ui_loop_end();
}
The compiler error:
error[E0499]: cannot borrow `wctx` as mutable more than once at a time
--> bambu-ui/src/os_spec/apple/ios/windows.rs:165:93
|
165 | let mut pevent = PointerEvent::new(pointer_id, &mut wctx);
| ^^^^^^^^^
| |
| `wctx` was mutably borrowed here in the previous iteration of the loop
| first borrow used here, in later iteration of loop
It seems like it is not borrowed “more than once at a time”. The event borrows it, but is destroyed within each iteration. Why does it think there is a problem?
What are the function signatures of PointerEvent::new and pointer_down?
impl<'w> PointerEvent<'w> {
fn new(pointer_id: PointerId, wctx: &'w mut WindowCtx<'w>) -> PointerEvent<'w>;
}
fn pointer_down(state: &mut State, event: &mut PointerEvent);
I’m leaving out a few very simple Copy args to new but I’m pretty sure they’re not relevant.
I see the WindowCtx was constructed from an mut reference of self.ctx_state, so it's probably a lifetime issue, as &'a mut T is covariant over 'a, but invariant over T, i.e. the type WindowCtx<'b> contains a lifetime 'b that outlives the loop body.
to help understand the problem, please show the signature of fn next_object() and fn pointer_down(), as well as the definition of the type WindowCtx and PointerEvent.
1 Like
next_object is an Objective-C FFI thing. It’s just returning a wrapper around an FFI raw pointer with no lifetimes or refs involved.
fn next_object(&self) -> Option<Object>
PointerEvent has some simple fields like ints and points but here is the part that probably matters:
struct PointerEvent<'w> {
pointer_id: PointerId,
...
wctx: &'w mut WindowCtx<'w>,
The problem is the &'w mut WindowCtx<'w> parameter.
The existence of the 'w lifetime twice here causes rust to think that, after the new() function is called, the WindowCtx might be mutated so that it mutably borrows from itself. This causes the WindowCtx to be unusable until the borrower is gone. However, since the borrower is itself, the WindowCtx becomes unusable until the WindowCtx itself is gone.
Try changing the type here to either &mut WindowCtx<'w> or &'w WindowCtx<'_>.
2 Likes
Ugg, my brain is in knots. I need to sleep and then look at this tomorrow. I can see something is not right with using 'w too many times.
Oh, I think your PointerEvent needs to have two different lifetime parameters. One lifetime represents how long the PointerEvent has access to the WindowCtx. The other lifetime represents how long the WindowCtx has access to whatever it's borrowing.
1 Like
this is the problem: the presence of type &'w mut WindowCtx<'w> alone will cause the problem, and it is an common anti-pattern in rust:
you might work around this case by having two lifetimes of the PointerEvent type, and the function signatures too, as already suggested, but generally, I would consider it a smell, if data types have complex lifetime relations, especially when multiple nested mut references are involved.
I think something like this could "fix" the multiple borrows in a loop error, but without knowing the details of the design, it may cause errors in other places:
struct PointerEvent<'a, 'b> {
//...
wctx: &'a mut WindowCtx<'b>,
}
impl PointerEvent<'_, '_> {
fn new<'a, 'b>(pointer_id: PointerId, wctx: &'a mut WindowCtx<'b>)-> PointerEvent<'a, 'b>;
}
fn pointer_down(state: &mut State, event: &mut PointerEvent<'_, '_>);
note, this is not the only way to write it, this is just a demo that multiple lifetimes are messy to work with. so consider to refactor and cleanup the design if possible.
2 Likes