Delegate trait implementations with procedural macros

It’s annoying to have to (I think) write something like this in order to implement an efficient iterator newtype over a slice iterator:

#[derive(Clone, Debug)]
pub struct Nodes<'a, H: 'a, T: 'a>(slice::Iter<'a, Node<H, T>>);

impl<'a, H, T> Iterator for Nodes<'a, H, T> {
    type Item = &'a Node<H, T>;
    fn next(&mut self) -> Option<Self::Item> { self.0.next() }
    fn size_hint(&self) -> (usize, Option<usize>) { self.0.size_hint() }
    fn count(self) -> usize { self.0.count() }
    fn nth(&mut self, n: usize) -> Option<Self::Item> { self.0.nth(n) }
    fn last(self) -> Option<Self::Item> { self.0.last() }
}

impl<'a, H, T> ExactSizeIterator for Nodes<'a, H, T> {
    fn len(&self) -> usize { self.0.len() }
}

impl<'a, H, T> DoubleEndedIterator for Nodes<'a, H, T> {
    fn next_back(&mut self) -> Option<Self::Item> { self.0.next_back() }
}

(A couple more trait impls to add if you want to be good with nightly Rust.)

It would be much nicer to be able to do it like this:

#[derive(Clone, Debug)]
#[delegate(Iterator, ExactSizeIterator, DoubleEndedIterator)]
pub struct Nodes<'a, H: 'a, T: 'a>(slice::Iter<'a, Node<H, T>>);

For multi-member structures and tuples, the member could be specified with a parameter:

#[delegate(member=inner; Iterator, ExactSizeIterator, DoubleEndedIterator)]
pub struct Nodes<'a, H: 'a, T: 'a> {
    inner: slice::Iter<'a, Node<T>>,
    foo: H
}

Of course it’s not possible to automagically do it for any trait: Iterator, for one, has skip() and similar object-unsafe methods. This is where procedural macros might be able to help. For object-safe traits the compiler might just generate delegation support implicitly.

Delegation has been discussed here before. Has anyone considered using procedural macros for this? Worth an RFC?

5 Likes

I think there’s appetite for full, in-language syntax for this. It’s “an important problem the lang team would like to tackle” per

https://github.com/rust-lang/rfcs/pull/1406

See also this newer delegation thread that was working on a new design.

2 Likes

Would be interesting to see if current derives can already tackle this in a reasonable way.

There even exists (definition, usage) workarounds with plain macros, and I’d like to replace them with something that does not break source level tools as bad (like show source of trait or racer).

I favor a new attribute name for three reasons:

  • The intent of the auto-generated impl is much clearer;
  • For procedural macro implementations, the two different forms of derive would result in quite different code: the current meaning of derive is “go over all components and come up with the sensible aggregate impl”, whereas for delegation it is “gelegate the impl to the member, or the specified member”.
  • If insta-delegation for object-safe traits is supported in the future, the separate attribute name would make reasoning about the behavior much clearer.

The syntax was debated multiple times. Using a specific attribute has serious limitations.

Unfortunately I'm afraid this feature is currently at a standstill. People do agree it would be a really nice improvement but as far as I know nobody is still working on it.

I've done something similar to this in diesel-derive-newtype, which also requires a whole bunch of implementations of trait implementations to function as a transparent proxy. Based on my experience with that, it would be straightforward to write something like that allows #[derive(IteratorNewType)] and have it work correctly.

I’ve thought of something more powerful that can work with multi-parametric traits:

delegate_impl! { PartialEq for
    Foo { |lhs| &lhs.inner }
    Bar { |rhs| rhs.as_inner() }
}
1 Like

Info-update: the delegation RFC is where it’s at in the community, proposing additions to the language syntax to sugar up simple cases.

1 Like

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