Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are the rules for blanket implementations regarding modules?

I've been trying to find documentation on how Rust resolves trait blanket implementations in case module boundaries are involved, but didn't find much directly related to it.

Let's consider an example with two similar, but slightly different code snippets:
The first one is code that does compile just fine, but suffers an infinite runtime recursion. This happens because the blanket implementation of the ClonableIterator for T: Iterator+Clone matches against the Box<dyn ClonableIterator<'a>> which is, thanks to our manual implementation of Clone and Box's blanket implementation of Iterator now Iterator+Clone.

//the trait itself, with the indirection to call box_clone on the base type
trait ClonableIterator<'a>: Iterator<Item = &'a u32> + 'a {
    fn box_clone(&self) -> Box<dyn ClonableIterator<'a>>;
}

//The blanket implementation of the trait for all clonable iterators
impl<'a, T: Iterator<Item = &'a u32> + Clone + 'a> ClonableIterator<'a> for T {
    fn box_clone(&self) -> Box<dyn ClonableIterator<'a>> {
        Box::new(self.clone())
    }
}

//Clone for the box. Does not work
impl<'a> Clone for Box<dyn ClonableIterator<'a>> {
    fn clone(&self) -> Self {
      (*self).box_clone()
    }
}


fn main() {
    let input = vec![1,2,3,4,5];
    let iter1 : Box<dyn ClonableIterator> = Box::new(input.iter());
    let iter2 : Box<dyn ClonableIterator> = Box::new(iter1.clone().filter(|&&x| x%2 == 0));
    println!("{} {}", iter1.sum::<u32>(), iter2.sum::<u32>())
}

(Playground)

The second snippet is just a slight modification of the above, namely the implementation of the Clone trait has been moved to a submodule. However, it is working and doing what one expects. Trait objects created of ClonableIterator are both, Clone and Iterator, and calling the clone() method on them creates an actual copy of the boxed iterator.
Both, Iterator methods and clone() can be directly called on the Box<dyn ClonableIterator>, and iterators created from it are again Clone+Iterator, so a ClonableIterator trait object can be created from them.

//the trait itself, with the indirection to call box_clone on the base type
trait ClonableIterator<'a>: Iterator<Item = &'a u32> + 'a {
    fn box_clone(&self) -> Box<dyn ClonableIterator<'a>>;
}

//The blanket implementation of the trait for all clonable iterators
impl<'a, T: Iterator<Item = &'a u32> + Clone + 'a> ClonableIterator<'a> for T {
    fn box_clone(&self) -> Box<dyn ClonableIterator<'a>> {
        Box::new(self.clone())
    }
}

//a helper module, to prevent the above blanket implementation from matching
//the Box<dyn super::ClonableIterator<'a>>.
//I can't find the documentation how this works, but it does work.
mod helper {
    impl<'a> Clone for Box<dyn super::ClonableIterator<'a>> {
        fn clone(&self) -> Self {
          (*self).box_clone()
        }
    }
}

fn main() {
    let input = vec![1,2,3,4,5];
    let iter1 : Box<dyn ClonableIterator> = Box::new(input.iter());
    let iter2 : Box<dyn ClonableIterator> = Box::new(iter1.clone().filter(|&&x| x%2 == 0));
    println!("{} {}", iter1.sum::<u32>(), iter2.sum::<u32>())
}

(Playground)

Could someone please explain or maybe link relevant documentation on how the rules for applying a trait blanket implementation differ from the rules for having the trait itself available?

like image 829
soulsource Avatar asked Nov 05 '22 23:11

soulsource


1 Answers

I don't have a complete citation of the reference manual for all the parts here, but the difference I see between these two cases is ClonableIterator is not visible in mod helper (there is no use super::ClonableIterator). Thus, the trait is not consulted for arbitrary types when looking up methods, but (evidently) it is for dyn super::ClonableIterator, presumably because it would be gratuitously unhelpful to not do that.

So the method lookup procedure will be doing approximately this:

  1. We start with *self, an explicit dereference of &Box<dyn CloneableIterator<'a>> of type Box<dyn CloneableIterator<'a>>.
    • Does this have an inherent method named box_clone()? No.
    • Does any visible trait have a method named box_clone()?
      • In the first program, yes (ClonableIterator was declared in the same scope), so that one is called.
      • In the second program, no, so continue.
  2. Dereference Box<dyn CloneableIterator<'a>> to obtain dyn CloneableIterator<'a>.
    • Does this have an inherent method named box_clone()? No.
    • Does any visible trait have a method named box_clone()? Yes, apparently CloneableIterator is visible for the dyn type unconditionally.
like image 188
Kevin Reid Avatar answered Dec 19 '22 22:12

Kevin Reid