Recursive generic constraint on Self

I want a recursive function that is called do() to be common to a family of types. I also need each type to have a function that returns a type with the same implementation as itself.

struct A;
struct B;
struct C;

impl A { fn do(&mut self, path: Path) -> Option<Path> { ... } fn next(&mut  self) -> _? { ... } }
impl B { fn do(&mut self, path: Path) -> Option<Path> { ... } fn next(&mut  self) -> _? { ... } }
impl C { fn do(&mut self, path: Path) -> Option<Path> { ... } fn next(&mut  self) -> _? { ... } }

But I don't want to rewrite the same function in a separate impl for each type. A trait wont help here because it just makes sure that I do write the same implementation for each type.

So I made a wrapper type and put the next fn in a trait.

trait Next {
    type Inner;
    fn next(&mut self) -> Option<Self::Inner>;
}

struct Node<T>(T);

impl Next for Node<A> {
    type Inner = Node<B>;
    fn next(&mut self) -> Option<Self::Inner> { ... }
}
impl Next for Node<B> {
    type Inner = Node<C>;
    fn next(&mut self) -> Option<Self::Inner> { ... }
} 
impl Next for Node<C> {
    type Inner = Node<A>;
    fn next(&mut self) -> Option<Self::Inner> { ... }
}

impl<T, T2> Node<T>
where
    Self: Next<Inner = Node<T2>>
{
    fn do(&mut self, path: Path) -> Option<Path> {
        match path {
            Path::Here => {
                let next_node = self.next();
                // stuff
            },
            Path::Next(path) => {
                let next_node = self.next().unwrap();
                next_node.do(path); // Error
            }
        };

        todo!()
     }
}

This doesn't work. The next_node.do(path) is the problem. I need self.next() to return the next node based on a specific impl Node<A> | Node<B> | Node<C>. And I also need the thing that returns to be a Node<_> that I can call do(&mut self, path: Path) on. I thought Self: Next<Inner = Node<T2>> constraint would do it. As in I assumed Node<T> == Node<T2> such that a Node<T2> would recursively use the impl impl<T, T2> Node<T2> in which it is constrained in. So now I am stuck.

Does this work for you?

trait Next {
    type Inner: Do; // <-- new bound
    fn next(&mut self) -> Option<Self::Inner>;
}

trait Do {
    fn r#do(&mut self, path: Path) -> Option<Path>;
}

impl<T> Do for Node<T>
where
    Self: Next,
{
    fn r#do(&mut self, path: Path) -> Option<Path> {
        // same as you had before
    }
}
2 Likes

It does thank you. The drawback to this is I need to "close" the Next::Inner type for when I have a sequence of Next implementers that eventually lead to a terminal implementation.

    trait Do {
        fn r#do(&mut self);
    }

    trait Next {
        type Inner: Do;
        fn next(&mut self) -> Option<Self::Inner>;
    }

    struct Node<T>(T);

    impl Next for Node<u8> {
        type Inner = Node<u32>;
        fn next(&mut self) -> Option<Self::Inner> {
            None
        }
    }

    impl Next for Node<u32> {
        type Inner = Node<u64>;
        fn next(&mut self) -> Option<Self::Inner> {
            None
        }
    }

    impl Next for Node<u64> {
        type Inner = ();
        fn next(&mut self) -> Option<Self::Inner> {
            None
        }
    }

    impl Do for () {
        fn r#do(&mut self) {
            unreachable!()
        }
    }

    impl<T> Do for Node<T> 
        where
            Self: Next
    {
        fn r#do(&mut self) 
        {
            self.next().unwrap().r#do();
        }
    }

impl Next for Node<u64> shouldn't return anything from next(). But a type is still expected and one that impl Do. So I stuffed a dummy type ()

    impl Next for Node<u64> {
        type Inner = ();
        fn next(&mut self) -> Option<Self::Inner> {
            None
        }
    }

    impl Do for () {
        fn r#do(&mut self) {
            //unreachable!()
        }
    }

Well you have to terminate the recursion somehow in any case. This is not really a drawback, it's a natural and inevitable necessity.

1 Like

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.