"Nested" impls as a solution to generics boilerplate

We have these RAII/transaction things:

struct SubtreeHelper<'r, 's, P: Borrow<str> + Ord, O: Borrow<str> + Ord, T: PatternTypes> where Self: 'r {
    root: &'r mut Parser<'s, P, O, T>,
}

impl<'r, 's, P: Borrow<str> + Ord, O: Borrow<str> + Ord, T: PatternTypes> SubtreeHelper<'r, 's, P, O, T> where Self: 'r {
    fn start(value: &'r mut Parser<'s, P, O, T>) -> Self {
        value.consts.protos.push(Default::default());
        Self {
            root: value,
        }
    }

    fn commit(self) -> usize {
        let mut self_ = ManuallyDrop::new(self);
        let proto = self_.root.consts.protos.pop().unwrap();
        let id = self_.root.closed_subtrees.next().unwrap();
        self_.root.consts.protos.insert(id, proto);
        id
    }
}

impl<'r, 's, P: Borrow<str> + Ord, O: Borrow<str> + Ord, T: PatternTypes> std::ops::Deref for SubtreeHelper<'r, 's, P, O, T> where Self: 'r {
    type Target = Parser<'s, P, O, T>;

    fn deref(&self) -> &Self::Target {
        &*self.root
    }
}

impl<'r, 's, P: Borrow<str> + Ord, O: Borrow<str> + Ord, T: PatternTypes> std::ops::DerefMut for SubtreeHelper<'r, 's, P, O, T> where Self: 'r {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.root
    }
}

impl<'r, 's, P: Borrow<str> + Ord, O: Borrow<str> + Ord, T: PatternTypes> Drop for SubtreeHelper<'r, 's, P, O, T> where Self: 'r {
    fn drop(&mut self) {
        // remove "partial" proto
        self.root.consts.protos.pop().expect("SubtreeHelper");
    }
}

struct TagHelper<'r, 's, P: Borrow<str> + Ord, O: Borrow<str> + Ord, T: PatternTypes> where Self: 'r {
    root: &'r mut Parser<'s, P, O, T>,
    len: usize,
}

impl<'r, 's, P: Borrow<str> + Ord, O: Borrow<str> + Ord, T: PatternTypes> TagHelper<'r, 's, P, O, T> where Self: 'r {
    fn start(value: &'r mut Parser<'s, P, O, T>) -> Self {
        let len = value.consts.protos.last().unwrap().len();
        Self {
            root: value,
            len : len,
        }
    }

    fn commit(self) {
        std::mem::forget(self)
    }
}

impl<'r, 's, P: Borrow<str> + Ord, O: Borrow<str> + Ord, T: PatternTypes> std::ops::Deref for TagHelper<'r, 's, P, O, T> where Self: 'r {
    type Target = Parser<'s, P, O, T>;

    fn deref(&self) -> &Self::Target {
        &*self.root
    }
}

impl<'r, 's, P: Borrow<str> + Ord, O: Borrow<str> + Ord, T: PatternTypes> std::ops::DerefMut for TagHelper<'r, 's, P, O, T> where Self: 'r {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.root
    }
}

impl<'r, 's, P: Borrow<str> + Ord, O: Borrow<str> + Ord, T: PatternTypes> Drop for TagHelper<'r, 's, P, O, T> where Self: 'r {
    fn drop(&mut self) {
        let proto = self.root.consts.protos.last_mut().unwrap();
        assert!(proto.len() >= self.len);
        while proto.len() > self.len {
            let _ = proto.pop();
        }
    }
}

They come with a lot of generics. This is less than ideal. Since Drop can't be specialized there's really no good way to get rid of the pointless generics for Deref and DerefMut. So ofc the next solution is to get rid of all of them. :‌p

It'd be cool to just do this:

struct SubtreeHelper<'r, 's, P: Borrow<str> + Ord, O: Borrow<str> + Ord, T: PatternTypes> where Self: 'r {
    root: &'r mut Parser<'s, P, O, T>,
}

impl<'r, 's, P: Borrow<str> + Ord, O: Borrow<str> + Ord, T: PatternTypes> SubtreeHelper<'r, 's, P, O, T> where Self: 'r {
    fn start(value: &'r mut Parser<'s, P, O, T>) -> Self {
        value.consts.protos.push(Default::default());
        Self {
            root: value,
        }
    }

    fn commit(self) -> usize {
        let mut self_ = ManuallyDrop::new(self);
        let proto = self_.root.consts.protos.pop().unwrap();
        let id = self_.root.closed_subtrees.next().unwrap();
        self_.root.consts.protos.insert(id, proto);
        id
    }

    impl std::ops::Deref {
        type Target = Parser<'s, P, O, T>;

        fn deref(&self) -> &Self::Target {
            &*self.root
        }
    }

    impl std::ops::DerefMut {
        fn deref_mut(&mut self) -> &mut Self::Target {
            self.root
        }
    }

    impl Drop {
        fn drop(&mut self) {
            // remove "partial" proto
            self.root.consts.protos.pop().expect("SubtreeHelper");
        }
    }
}

struct TagHelper<'r, 's, P: Borrow<str> + Ord, O: Borrow<str> + Ord, T: PatternTypes> where Self: 'r {
    root: &'r mut Parser<'s, P, O, T>,
    len: usize,
}

impl<'r, 's, P: Borrow<str> + Ord, O: Borrow<str> + Ord, T: PatternTypes> TagHelper<'r, 's, P, O, T> where Self: 'r {
    fn start(value: &'r mut Parser<'s, P, O, T>) -> Self {
        let len = value.consts.protos.last().unwrap().len();
        Self {
            root: value,
            len : len,
        }
    }

    fn commit(self) {
        std::mem::forget(self)
    }

    impl std::ops::Deref {
        type Target = Parser<'s, P, O, T>;

        fn deref(&self) -> &Self::Target {
            &*self.root
        }
    }

    impl std::ops::DerefMut {
        fn deref_mut(&mut self) -> &mut Self::Target {
            self.root
        }
    }

    impl Drop {
        fn drop(&mut self) {
            let proto = self.root.consts.protos.last_mut().unwrap();
            assert!(proto.len() >= self.len);
            while proto.len() > self.len {
                let _ = proto.pop();
            }
        }
    }
}
2 Likes

The problem of verbose impl generics should be addressed by implied bounds

2 Likes

hm...

we'd argue impl trait in impl struct would have more use-cases tho, and doesn't interact badly with inference (as it's just sugar, mostly). and now that referring to a trait in type position requires an explicit dyn, we can have this without ambiguity and stuff.

(it would also, potentially, make better docs, by grouping trait impls with the relevant impl blocks.)