Progrider

Programming discussion

Board index
Help · Open discussion

Rust: cannot push to Vec while I hold reference to element, but why

I learn Rust since two weeks. This does not compile: let mut v = vec![1, 2, 3]; let first = &v[0]; v.push(4); println!("{}", first); error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable I understand the message. I don't understand why is it a problem here, I am not changing element 0.

13 replies

push might reallocate. If the Vec is at capacity it allocates a bigger buffer, moves everything and frees the old one. Then first points at freed memory. The borrow checker doesn't know whether this particular push will reallocate, so it rejects it always. That's the entire reason. Same bug in C++ would compile and then bite you at runtime.
Can confirm the C++ part, I have the scars.
OK this makes sense. But if I swap the last two lines it compiles. So the borrow ends at the last use and not at end of the block?
Yes. Non-lexical lifetimes. The borrow lasts until the last use.
Next problem then. My real code is a list of particles, and each one should look at its neighbours and change itself. for p in particles.iter_mut() { for q in particles.iter() { ... } } Same error of course. What is the normal way to do this?
Indices, usually. for i in 0..particles.len() { for j in 0..particles.len() { if i == j { continue; } let d = particles[j].pos - particles[i].pos; particles[i].vel += d * k; } } Each indexing is a short borrow so there's no conflict. d is computed first and it's a plain value. People will tell you this is cheating. It isn't.
"Rewrite it in Rust" -> rewrites it in array indices, like Fortran
vek is right in spirit, you do end up reinventing handles and arenas. Though that's how a lot of good C ended up as well.
It works. There is also split_at_mut in documentation, is that better?
For pairs from two separate halves, yes. For all pairs like you have, it just makes it harder to read. If you need to update both at once then split_at_mut, or swap to computing a list of deltas in one pass and applying them in a second. The second is often what you want for a simulation anyway, otherwise particle 0 moves before particle 1 has looked at it.
Oh. That was a bug I had and didn't know. Thank you.
Same two pass thing applies in numpy, compute everything from the old array then assign. Learned that from a very wrong heat diffusion plot.
Coming back to say a month later I am using Rust at work for one small service. I still use indices. See also the thread on tokio mutex, I found new way to confuse myself.

Add a reply

Plain text. Public, and removed when the thread expires.

Posts and requests are logged for research. Privacy notice.