Recursive default associated types for Trait objects

Hi, I'm running into a situation which I'd like some assistance on;

I've got a trait Element which ideally defines a property for a container of child Elements (recursive), but since traits can't do that, the implementation I'm hoping to use is:

trait Element {
    type Children: IntoIterator<dyn Element> = std::vec::IntoIter<dyn Element>;

    fn get_children(&self) -> Self::Children;
}

Now I'm aware there are a few issues with this code, specifically with the whole dyn situation, but I'm hoping for advice on how I would represent elements in this way - Any implementor of Element can choose how it stores its children, as long as there is a way to iterate over them.

Because, ideally, I'd like to use the trait like so:

struct GridLayout { 
    // ...
}
impl Element for GridLayout { 
    type Children = HashMap<String, Box<dyn Element>>; // I'm required to specify the associated type `Children` here. 
    fn get_children(&self) -> Self::Children {
        self.children.into_iter().map(|(area, child)| child)
    }
    // ...
}

My question is how can I make the associated type Children optional, using the default type (std::vec::IntoIter<dyn Element>) if omitted?

If necessary, some tips to restructure my trait to allow for such behaviour would be greatly appreciated.

Thanks everyone

There's a few things going on here. To answer one of the direct questions, associated type defaults are unstable, and probably needs a champion to go anywhere.

But I'm not sure exactly what you're looking for in the trait.

  • Do you need to know what the container is? The second example specifies a container type as Children but then returns a (non-nameable) iterator
  • Are you looking for an owning iterator or a borrowed iterator here? get_children takes &self, but in your examples you're returning Box<dyn Element> which implies ownership and thus cloning or interior mutability

If I had to guess, you're wishing you had something like

trait Element {
    // Maybe you even wish it was `impl Element + '_`
    fn get_children(&self) -> impl Iterator<Item = &'_ dyn Element> + '_;
}

Which is a feature called RPITIT (return-position impl Trait in Trait), which is a mouthful. It's an active area of interest as it will pave the way for async fn in traits, but it's still a ways off yet.

Instead for now, you do the sort of type-erasing (dyn Trait) things you've mentioned here.

Is that accurate? Or more concretely,

  • Do you ever need to know the concrete type of the children / container / iterator?
  • Do you need owning or borrowing iterators (or both)?
1 Like

Hi @quinedot thanks for your reply

You've made a few interesting points which I'd never even considered:

  1. Owned iterator vs borrowed iterator
  2. Do I need to know what the container is
  3. Do I need to know what the child is

The last two points, both times, the answer is no. I'd like it to be as general as possible, but really allow any type which implements Element

I'd also like to offer freedom in how children are stored, so ideally, I don't know the concrete type of the Iterator,

Because the children will always belong to the element, It'd be better if the iterator were over a borrowed type, but the iterator itself can be owned, that doesn't really matter.

To be honest, I'm still fairly new to Rust - I can find my way around, but I'm by no means confident, and this is a prime example of this. I wasn't aware of any other way of achieving this behaviour (Hence I ask here), however the snippet you provided looks exactly like something I would need.

Thanks again for your help

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.