Sharing logic for search methods between mutable and immutable cases

Just started learning rust recently. One of the big problems I seem to run into over and over again is that I'll write some "search" logic to perform a lookup operation on a struct, and I'll want the result of that operation to be a &reference or &mut reference depending on context. An example:

pub fn attackers(&self) -> impl Iterator<Item = &Creature> {
    self.creatures
        .iter()
        .filter(|c| matches!(c.state, CreatureState::Attacking(_)))
}

I sometimes want this method to return an iterator over &Creature, but sometimes want it to iterate over &mut Creature. Another example:

pub fn player(&self, name: PlayerName) -> &Player {
    match name {
        PlayerName::User => &self.user,
        PlayerName::Enemy => &self.enemy,
    }
}

I get that this isn't a whole lot of code to copy and paste, but it seems to be happening to me a lot. It's really been the only recurring annoyance in my otherwise quite positive experience learning the language. Is there anything I can do to avoid this kind of repeated boilerplate?

1 Like

I don't know of any good solution for this, though it's weird: there are some rust programs where I run into it a lot, and then others where it is never an issue. Not sure what the difference is.

The best "solution" I know of is to use a macro to automatically duplicate the code. The simplest version of this is to create a macro per function you want to duplicate, then call it twice, like so:

macro_rule! def_attackers {
    ($name:ident, $iter:ident, $($r:tt)*) => {
        impl X {
            pub fn $name($($r)* self) -> impl Iterator<Item = $($r)* Creature> {
                self.creatures
                    .$iter()
                    .filter(|c| matches!(c.state, CreatureState::Attacking(_)))
            }
        }
    }
}
def_attackers!(attackers, iter, &);
def_attackers!(attackers_mut, iter_mut, &mut);

I don't know if that's really worth it though, especially if the function is small enough. It might be possible to make a more general-purpose macro which does this kind of duplication, but even with something that could be written with less overhead, I don't know if the added complexity would be worth it?

It is a problem rust doesn't have the exact solution for today.

1 Like

Huh, we've had quite a few of these threads in a row. First this one with callbacks, and then this one about how it goes wrong if you try to cheat it with unsafe, and now this.

Unfortunately the answer is that unless you repeat the code with the macro, no, you can't avoid it.

2 Likes

I wonder, could be some common article or blog post many new rustaceans are coming from which leads to this particular question?

I remember something similar happening last December with many very similar questions about deref borrowing semantics from people learning rust while attempting a particular advent of code question.

Not that there's a need to investigate that, though. Regardless of the reason for coming across this, it is an annoyance, and it doesn't have any good workarounds.

2 Likes

Rust is strict about mutability and referencing other objects. An OOP-style game in Rust is going to run into a lot of such problems. I recommend this talk about it:

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.