Hello,
I'm pretty new to Rust, mainly used to OOP development in higher-level languages like PHP, Python, etc, so I haven't had to deal with ownership and borrows before. My project is a controller for some Neopixel LEDs driven by an RP2040 microcontroller that knows where each LED is in a 2D grid to display effects, but I think this question is fundamental to how Rust works, not specifically about embedded systems. Here, I'm trying to simulate spotlights by lighting up all the LEDs in a given circle. The spotlights randomly appear, fade in, then move around, and eventually stop moving and fade out.
Here is one version of my code:
pub fn tick(&mut self, draw_helper: &mut Draw, tick_count: usize) {
for (id, light) in self.spotlights.iter_mut() {
// Move the light and update its status
if matches!(light.mode, SpotlightMode::FADE_IN) {
// Do stuff
} else if matches!(light.mode, SpotlightMode::ACTIVE) {
// Do stuff
} else if matches!(light.mode, SpotlightMode::STOP) {
// Do stuff
}
// Draw the light
}
self.spotlights
is a BTreeMap of random IDs and a struct that holds the light's state, location, colour, etc. I'd use a HashSet, but it looks like this isn't available in embedded-alloc, and I can't guarantee that every light is comparable to put them in a BTreeSet, so I used the map instead.
This code compiles and runs, but there is a bug: the spotlights seem to disappear when the state changes from FADE_IN
to ACTIVE
, and I want to write unit tests to test this code on my PC. So I tried to refactor this tick
method to split out the draw function and each of the blocks I've marked as // Do stuff
. But this means I need a mutable borrow of the SpotlightStatus
(light
inside the for loop). The compiler is happy with me modifying the values inside that struct, but not having a second mutable borrow of the struct itself while inside that iterator:
error[E0499]: cannot borrow `*self` as mutable more than once at a time
--> src/scene/spotlights.rs:172:13
|
171 | for (id, light) in self.spotlights.iter_mut() {
| --------------------------
| |
| first mutable borrow occurs here
| first borrow later used here
172 | self.handle_light(light);
| ^^^^ second mutable borrow occurs here
error[E0502]: cannot borrow `*self` as immutable because it is also borrowed as mutable
--> src/scene/spotlights.rs:173:13
|
171 | for (id, light) in self.spotlights.iter_mut() {
| --------------------------
| |
| mutable borrow occurs here
| mutable borrow later used here
172 | self.handle_light(light);
173 | self.draw_light(*id, light, draw_helper);
| ^^^^ immutable borrow occurs here
I tried this both by defining methods on the controller (as here), and defining methods on the SpotlightStatus
struct (a more OOP style, but not great here because only the controller has an instance of tinyrand
available).
So, in either a general sense or in this specific case, is there a way to pass a mutable borrow of structs in a collection when iterating through that collection? I understand the problems you can create when doing this, e.g. by deleting the object, or changing it so that the collection is affected, like changing ordering of a tree. Is there a workaround to make it safe, or do I need a fundamentally different approach? I really do want to avoid having everything in a single function, though, so I can unit test it.
Suggestions of better collections are also welcome. I'm expecting only around 0-9 objects active at once, I don't care about order, and they need to be created and destroyed randomly.
Thanks