Why is Iterator object-safe?

Hello,

I have a kind of Iterator in my code, and I want to have a map function just like there is one for the standard Iterator:

pub(crate) type TokenResult<I> = Result<Token<I>, ParseError>;

pub trait TokenIter<I>: Iterator<Item=TokenResult<I>> {
    fn map<J, F: FnMut(I) -> J>(self, f: F) -> Map<I, J, F>;
}

impl<T, I> TokenIter<I> for T where T: Iterator<Item=TokenResult<I>> {
    fn map<J, F: FnMut(I) -> J>(self, f: F) -> Map<I, J, F> {
        todo!()
    }
}

Also, I want to use my TokenIter for trait objects like this:

    fn parse(&self, token_iter: Box<dyn TokenIter<I>>) -> Result<A, ParseError>;

Rust tells me this does not work, because map has a type parameter and thus makes TokenIter not object-safe:

error[E0038]: the trait `TokenIter` cannot be made into an object
  --> src/parser.rs:5:37
   |
5  |     fn parse(&self, token_iter: Box<dyn TokenIter<I>>) -> Result<A, ParseError>;
   |                                     ^^^^^^^^^^^^^^^^ `TokenIter` cannot be made into an object
   |
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
  --> src/token/token_iter.rs:10:8
   |
9  | pub trait TokenIter<I>: Iterator<Item=TokenResult<I>> {
   |           --------- this trait cannot be made into an object...
10 |     fn map<J, F: FnMut(I) -> J>(self, f: F) -> Map<I, J, F>;
   |        ^^^ ...because method `map` has generic type parameters
   = help: consider moving `map` to another trait

I was hoping to take inspiration from the standard Iterator and its map method, but I can't figure out why Iterator is object-safe even though map has type parameters?

Thanks!

Best, Oliver

Let's look at the full signature for Iterator::map()

That where Self: Sized clause means the map() method will only be available when Self has a known size, allowing the rest of the interface to be object-safe.

Thanks for pointing out Sized, but what's the connection between object safety and known size?

Trait objects don't implement Sized, so if you require Self: Sized in a method it won't be included in the trait object. This also means it won't be considered when checking if the trait is object-safe, but note that this also means you won't be able to call it on a trait object. The stdlib solved this final problem by implementing Iterator for Box<dyn Iterator> and &mut dyn Iterator by reimplementing all the methods in terms of next, which is object safe and thus included in the trait object.

4 Likes

Ah, thanks, that makes sense!
I guess in my case, I will implement map not on TokenIter<I>, but instead on Box<dyn TokenIter<I>>.

Actually, I can't implement Box<dyn TokenIter<I>>, because it's not my type. Maybe I'll work around by making TokenIter<I> a struct that owns a Box<dyn Iterator<Item=TokenResult<I>>>. Not what I hoped for, but what can I do.

You can't have a bare impl block, but you should be able to implement traits for it as long as you own the TokenIter trait.

You could just copy the stdlib, adding where Self: Sized to map and then implementing TokenIter<I> for Box<dyn TokenIter<I>>. This will work because you're implementing your own trait for another type.

Oh, cool, thanks, I didn't think of impl TokenIter<I> for Box<dyn TokenIter<I>>.

Assuming TokenIter is defined in your crate, you can do that, because Box is a fundamental type.

1 Like