Why does Rust allow you to call `Iterator::for_each()` on a trait object?

Viewed 185

I was playing around with some API concepts and noticed something peculiar in Rust's Iterator trait.

I have the following trait definition:

trait Observable {
    type Item;

    fn subscribe<F>(self, f: F) -> bool
    where
        Self: Sized,
        F: FnMut(Self::Item) + 'static;
}

I then proceeded to write the following test:

#[test]
#[should_panic]
fn trait_obj() {
    let mut v: Vec<Box<dyn Iterator<Item = ()>>> = vec![];
    let mut v2: Vec<Box<dyn Observable<Item = Ref<u8>>>> = vec![];

    v.remove(0).for_each(|_| {});
    v2.remove(0).subscribe(|_| {});
}

The above test does not compile, as one would expect; subscribe() takes self by value, and has a Sized constraint on Self, therefore is not object safe. However, if I comment out the ...subscribe line, it does compile!

The odd thing to me is, Iterator::for_each() has the same constraints. Why is this allowed for Iterator and not for Observable? Is it an experimental feature that enables this?

Here is the function signature of Iterator::for_each for reference:

// Iterator::for_each
fn for_each<F>(self, f: F)
where
    Self: Sized,
    F: FnMut(Self::Item);

The function signatures for Iterator::for_each and Observable::subscribe are pretty much identical.

What gives?

2 Answers

Your mistake is that you think you call <dyn Iterator<Item = ()>>::for_each(), and then you wonder rightfully how you can if for_each() requires Self: Sized but dyn Iterator<Item = ()> is obviously not Sized. But you are wrong. And you can see that if you'll use UFCS (Universal Function Call Syntax):

#[test]
#[should_panic]
fn trait_obj() {
    let mut v: Vec<Box<dyn Iterator<Item = ()>>> = vec![];

    // v.remove(0).for_each(|_| {});
    <dyn Iterator<Item = ()>>::for_each(v.remove(0), |_| {});
}

Playground.

Emits:

error[E0277]: the size for values of type `dyn Iterator<Item = ()>` cannot be known at compilation time
   --> src/lib.rs:7:5
    |
7   |     <dyn Iterator<Item = ()>>::for_each(v.remove(0), |_| {});
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
    |
    = help: the trait `Sized` is not implemented for `dyn Iterator<Item = ()>`
note: required by a bound in `for_each`

error[E0308]: mismatched types
 --> src/lib.rs:7:41
  |
7 |     <dyn Iterator<Item = ()>>::for_each(v.remove(0), |_| {});
  |                                         ^^^^^^^^^^^ expected trait object `dyn Iterator`, found struct `Box`
  |
  = note: expected trait object `dyn Iterator<Item = ()>`
                   found struct `Box<dyn Iterator<Item = ()>>`
help: consider unboxing the value
  |
7 |     <dyn Iterator<Item = ()>>::for_each(*v.remove(0), |_| {});
  |                                         +

error[E0277]: the size for values of type `dyn Iterator<Item = ()>` cannot be known at compilation time
 --> src/lib.rs:7:41
  |
7 |     <dyn Iterator<Item = ()>>::for_each(v.remove(0), |_| {});
  |                                         ^^^^^^^^^^^ doesn't have a size known at compile-time
  |
  = help: the trait `Sized` is not implemented for `dyn Iterator<Item = ()>`
  = note: all function arguments must have a statically known size

And this error also hints you why the previous version worked: you didn't call <dyn Iterator<Item = ()>>::for_each(), you called Box::<dyn Iterator<Item = ()>>::for_each()! Box<Iterator> implements Iterator itself, and so it worked. You can see that explicitly in the MIR:

v.remove(0).for_each(|_| {});
// Snip
_2 = <Box<dyn Iterator<Item = ()>> as Iterator>::for_each::<[closure@src/lib.rs:4:26: 4:32]>(move _3, move _5) -> [return: bb3, unwind: bb5];
// Snip

Playground (choose "Show MIR" from the menu).

If you has had implemented Observable for Box<O> where O: Observable, it would work for you too...

...Except you cannot. Because you cannot forward the call to for_each(). The reason it works with Iterator is that it does not forward this call, and rather uses the default implementation that calls next() again and again. And because next() takes &mut self, it doesn't require Self: Sized.

A similar question was asked over on the Rust forum. To summarize, the full signature for Iterator::for_each is

fn for_each<F>(self, f: F)
    where
        Self: Sized,
        F: FnMut(Self::Item);

It's the Self: Sized part that's key. That means "this function can only be called if Self has a known size", so for_each is explicitly disallowed on trait objects of type dyn Iterator. This is true of several of the methods on Iterator and is Rust's way of getting the best of both worlds: A trait that can still be used as an object but that has additional functionality available when it's being used statically.

I'm not sure why the Self: Sized is hidden from the Rustdoc page; you actually have to click "View Source" to see it, and that seems like an oversight to me.

Related