Rust Vector of Traits: cast each trait

2019-08-03 15:44发布

问题:

I have a problem casting a vector of traits into a vector of different traits.

Using the approach of Type-casting arrays/vectors in Rust , I basically tried the following:

trait ParentTrait {}

trait ChildTrait: ParentTrait {}

fn main() {
    let mut children: Vec<Box<ChildTrait>> = vec![];
    let parents = children.iter().map(|&e| e as Box<ParentTrait>);
}

Now this does not compile, it results in

error: the trait `core::kinds::Sized` is not implemented for the type `ChildTrait`
[...]
error: the trait `ParentTrait` is not implemented for the type `ChildTrait`
[...]

(The second errorline is buggy behaviour of the compiler, I guess?)

I tried various other flavors of References / Boxes and could not get it to work.

What am I doing wrong here, is this even the correct approach with newer versions of rust (0.13)?

回答1:

Trait objects are very strange beasts.

What is a Box<ChildTrait>? Box<T> is literally a wrapper for a *mut T. Therefore, a Box<ChildTrait> wraps a *mut ChildTrait. Because ChildTrait names a trait, ChildTrait is an object type. A pointer to an object type is represented by a pair of pointers: a pointer to the vtable for that trait and only that trait, and a pointer to the actual value.

When we inherit a trait from another trait, that doesn't mean we can obtain a pointer to the vtable for the first trait from a pointer to the vtable for the second trait. This is why the compiler complains that

the trait `ParentTrait` is not implemented for the type `ChildTrait`

We can, however, manually implement a trait for an object type. Because object types are unsized, we must first allow ParentTrait to be implemented for unsized types:

trait ParentTrait for Sized? {}

Then we can provide an impl of ParentTrait for the ChildTrait object type:

impl<'a> ParentTrait for ChildTrait+'a {}

If we try to compile now, we get different errors:

<anon>:9:40: 9:42 error: cannot move out of dereference of `&`-pointer
<anon>:9     let parents = children.iter().map(|&e| e as Box<ParentTrait>);
                                                ^~
<anon>:9:41: 9:42 note: attempting to move value to here
<anon>:9     let parents = children.iter().map(|&e| e as Box<ParentTrait>);
                                                 ^
<anon>:9:41: 9:42 help: to prevent the move, use `ref e` or `ref mut e` to capture value by reference
<anon>:9     let parents = children.iter().map(|&e| e as Box<ParentTrait>);

We can use into_iter instead of iter to consume the initial Vec:

fn main() {
    let mut children: Vec<Box<ChildTrait>> = vec![];
    let parents = children.into_iter().map(|e| e as Box<ParentTrait>);
}

But then we get an internal compiler error:

error: internal compiler error: trying to take the sizing type of ChildTrait, an unsized type
note: the compiler unexpectedly panicked. this is a bug.
note: we would appreciate a bug report: http://doc.rust-lang.org/complement-bugreport.html
note: run with `RUST_BACKTRACE=1` for a backtrace
task 'rustc' panicked at 'Box<Any>', /build/rust-git/src/rust/src/libsyntax/diagnostic.rs:175

The same error also occurs with this code:

fn main() {
    let mut children: Vec<Box<ChildTrait>> = vec![];
    let parents = children.iter().map(|e| &**e as &ParentTrait);
}

At this point, I don't know if, after fixing the ICE, this would compile successfully or not.