My .iter_mut() doesn’t work

Simplified version of my actual code:

pub trait Expression {
    fn iter(&self) -> Box<dyn Iterator<Item = Box<&dyn Expression>> + '_>;
    fn iter_mut(&mut self) -> Box<dyn Iterator<Item = Box<&mut dyn Expression>> + '_>;
}

pub struct Number {}
impl Expression for Number {
    fn iter(&self) -> Box<dyn Iterator<Item = Box<&dyn Expression>> + '_> {
        Box::new(std::iter::once(Box::new(self as &dyn Expression)))
    }
    fn iter_mut(&mut self) -> Box<dyn Iterator<Item = Box<&mut dyn Expression>> + '_> {
        Box::new(std::iter::once(Box::new(self as &mut dyn Expression))) // OK
    }
}

pub struct Addition {
    op1: Box<dyn Expression>,
    op2: Box<dyn Expression>,
}
impl Expression for Addition {
    fn iter(&self) -> Box<dyn Iterator<Item = Box<&dyn Expression>> + '_> {
        let self_iter = Box::new(std::iter::once(Box::new(self as &dyn Expression)));
        Box::new(self_iter.chain(self.op1.iter()).chain(self.op2.iter()))
    }
    fn iter_mut(&mut self) -> Box<dyn Iterator<Item = Box<&mut dyn Expression>> + '_> {
        let self_iter = Box::new(std::iter::once(Box::new(self as &mut dyn Expression)));
        Box::new(self_iter.chain(self.op1.iter_mut()).chain(self.op2.iter_mut()))
            // ERROR: cannot borrow `*self.op1` as mutable more than once at a time
    }
}
  • I want my code to be extensible. That’s why I went with a trait and not with an enum.
  • I understand why this doesn’t work but I don’t know how to fix it.
  • My .iter() works but seems relatively complicated. Is there a simpler way to implement it?

You can't have an iterator that returns overlapping &mut such as &mut self and &mut self.op1, because then it would be possible to .collect() them and have aliasing &mut (instant UB).

You could have a lending iterator (no trait in std for that yet), but it would have to be lazy (e.g. track state separately and serially construct and hand out &mut self, &mut self.op1, ...).

Side note: Box<&dyn Trait> and similar don't make much sense; just use &dyn Trait.

3 Likes

Maybe I'm wrong, but I recommend not reaching for trait objects (dyn Trait) when new to Rust. Most of the time, you won't need them.

1 Like

Just a few observations here.

I don't understand why you are using an iterator for these types. Iterators work best for collections of arbitrary size. There are some apparent exceptions, such as impl IntoIterator for Option. But even in that case the iterator has either 0 or 1 element, so it still fits the definition of arbitrary size. For a collection of fixed size, it would be more prudent to just provide accessor methods to the fields.

Extensibility is one thing. What I'm sensing here is more along the lines of duck typing. "Everything is an iterator that yields expressions." While Rust does have some types that can help with type erasure and reflection (e.g. Any and TypeId) it's usually reserved for special cases where there is really no other option. Best to leverage the type system as intended than try to work around it.

1 Like

Iterators work best for collections of arbitrary size.

This collection can have an arbitrary size (via nesting).

it's usually reserved for special cases where there is really no other option.

The actual code is a Markdown parser where people should be able to contribute new AST nodes. But you make a good point. Maybe the extensibility is not needed. An enum would make things much simpler.

1 Like

Another way to think of this (nesting) is that each leaf of the tree is still a fixed size, though the tree itself may be arbitrarily constructed. Or in other words, the tree is the collection, not the individual leaves. To this end, I would say the tree is a good candidate to offer an iterator, and the leaves do not need to implement Iterator to make this happen.

1 Like

Number (leaf) only provides an iterator so that Addition (collection) doesn’t have to distinguish between Additions and Numbers.

Addition is not the collection you are looking for. :slight_smile:

Concretely, the most common API design I have seen for iterating over an AST has been the visitor pattern. Which is, for instance, what serde provides for deserialization.

2 Likes

Visitor pattern is the way to go for these kinds of trees (e.g. ASTs). I think the syn crate implements a visitor pattern for traversing a Rust syntax tree (although the implementation is complicated). Probably the serde docs, especially those about implementing the traits manually will be most useful for you.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.