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?
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), |_| {});
}
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
.