Phil Lord
Phil Lord

Reputation: 3057

Type matching a BTreeSet inside a tuple in Rust

I have the code as follows (part of a larger library). The compiler is telling me that a tuple does not implement a trait, but I have both an implementation for a tuple and for the one element of that tuple. And it works with another type of tuple.

Why does the tuple (BTreeSet<Annotation>) not get matched here?

use std::collections::BTreeSet;

pub struct Axiom {}
pub struct Annotation {}

pub struct AnnotatedAxiom {
    pub axiom: Axiom,
    pub annotation: BTreeSet<Annotation>,
}

trait Render {
    /// Render a entity to Write
    fn render(&self);
}

impl<'a, T: Render> Render for &'a BTreeSet<T> {
    fn render(&self) {}
}

impl<'a, A: Render> Render for (&'a A,) {
    fn render(&self) {
        (&self.0).render();
    }
}

/// The types in `Render` are too long to type.
macro_rules! render {
    ($type:ty, $self:ident,
     $body:tt) => {

        impl Render for $type {
            fn render(& $self)
                $body
        }
    }
}

render!{
    Annotation, self,
    {
    }
}
render!{
    Axiom, self,
    {
    }
}

render!{
    AnnotatedAxiom, self,
    {
        // Axiom implements Render
        (&self.axiom).render();

        // Annotation implements Render
        (&self.annotation).render();

        // A 1-element tuple of Axiom implements Render
        (&self.axiom,).render();

        // A 1-element tuple of Annotation does!?
        (&self.annotation,).render();

    }
}

fn main() {}

Playground

error[E0599]: no method named `render` found for type `(&std::collections::BTreeSet<Annotation>,)` in the current scope
  --> src/main.rs:62:29
   |
62 |         (&self.annotation,).render();
   |                             ^^^^^^
   |
   = note: the method `render` exists but the following trait bounds were not satisfied:
           `(&std::collections::BTreeSet<Annotation>,) : Render`
   = help: items from traits can only be used if the trait is implemented and in scope
   = note: the following trait defines an item `render`, perhaps you need to implement it:
           candidate #1: `Render`

Upvotes: 1

Views: 229

Answers (1)

E_net4
E_net4

Reputation: 29983

There is a gap in the implementation chain:

impl<'a, T: Render> Render for &'a BTreeSet<T> {
    // ....
}

impl<'a, A: Render> Render for (&'a A,) {
    // ...
}

The first impl provides Render for a reference to a BTreeSet, whereas the second one provides an implementation for a tuple of a reference to something that implements Render. Since BTreeSet itself does not implement Render (only a reference to it does!), the compiler will refuse to work.

This is a situation where it's more ergonomic to abstract away from references, since Render appears to be fitting for any reference to another Renderable value. Implement this trait for all references &T where T: Render:

impl<'a, T> Render for &'a T
where
    T: Render,
{
    fn render(&self) {
        (**self).render();
    }
}

The remaining implementations become slightly simpler as a consequence of this:

impl<T> Render for BTreeSet<T>
where
    T: Render,
{
    fn render(&self) {}
}

impl<A> Render for (A,)
where
    A: Render,
{
    fn render(&self) {
        (&self.0).render();
    }
}

Working Playground

See also:

Upvotes: 3

Related Questions