How do I extract elements of a given type from a collection?

I am a beginner in Rust, and not good at English.
Sorry if there are any funny expressions.

In Rust...

  • Is it possible to extract elements of a given type from a collection of arbitrary element types?
  • Also, when specifying a type, is it possible to statically restrict it to a lower type of the collection type?

I feel that the code below represents the vibe of what I want to do, but it does not work at all.

use std::any::{Any, TypeId};

fn main() {
    let mut mix_vec = MixVec::<dyn MyTrait>::new();
    mix_vec.insert(Box::new(MyStruct1 { }));
    mix_vec.insert(Box::new(MyStruct2 { }));
    mix_vec.insert(Box::new(MyStruct2 { }));
    let selecteds = mix_vec.select_by_type::<MyStruct2>();
    println!("{}", selecteds.count());
}

trait MyTrait: Any {}
struct MyStruct1 {}
struct MyStruct2 {}
impl MyTrait for MyStruct1 {}
impl MyTrait for MyStruct2 {}

struct MixVec<T>
where T: ?Sized + Any
{
    base: Vec<Box<T>>,
}

impl<T> MixVec<T>
where T: ?Sized + Any
{
    pub fn new() -> Self {
        Self {
            base: Default::default(),
        }
    }

    pub fn insert(&mut self, vallue: Box<T>) {
        self.base.push(vallue);
    }

    pub fn select_by_type<X: Any>(&self) -> impl Iterator<Item = &Box<T>> {
        self.base.iter().filter(|&x| x.type_id() == TypeId::of::<X>())
    }
}

I do not understand what you mean by "a lower type of the collection type".

Apart from that, you have a few errors and unnecessary complications in your code:

  1. You want a collection of dynamically-typed elements. Why the generic argument, then? You are specifically trying to opt out of static typing, so requiring a single generic type doesn't make any sense. You should just use Box<dyn Any>.
  2. The only thing generic here should be the insert method: that works with any static type, which it then erases by putting it behind a Box<dyn Any>.
  3. If you have a concrete type that implements Any but is not dyn Any (the concrete type, in your case dyn MyTrait), and you try to check the type_id() of it, then it will coerce to Any and you will never get a match or a succeeding downcast, because a trait object type is a distinct type from every underlying concrete type it was created from. You will not get any matches either if you get the levels of indirection wrong (e.g. Box<T> won't downcast to T, neither will &T, etc.)
  4. It's useless and actually harmful to return &Box<T>. It gives you no more capabilities as a plain &T, it exposes an implementation detail, and it leads to unnecessary double indirection.
  5. Instead of explicitly comparing TypeIds, you can use Any::is::T<>() to check against a type. If you want to filter on them and downcast the eligible elements to the requested concrete type, it's even better to .downcast_ref() and then .filter_map().

Fixing these errors, and simplifying-rationalizing the code further yields the following, which works as expected:

struct MixVec {
    base: Vec<Box<dyn Any>>,
}

impl MixVec {
    pub fn insert<T: Any>(&mut self, value: T) {
        self.base.push(Box::new(value));
    }

    pub fn select_by_type<T: Any>(&self) -> impl Iterator<Item = &T> {
        self.base.iter().filter_map(|x| x.downcast_ref::<T>())
    }
}
5 Likes

Thank you for reading my poor English.

It seems that some of the expressions were not appropriate. My apologies.

  • First, I meant "a lower type of the collection type" as "a subtype of the element type (MyTrait) in the collection". ...In Rust's language, is it more appropriate to say "the type that implements the trait" rather than "subtype"?

  • And I specified dyn MyTrait as the type parameter for MixVec because I wanted to restrict the types that insert into the MyTrait subtype more than Any (Maybe the expression "arbitrary element types" was inappropriate).

To reword my previous statement, here is what I would like to achieve.

  1. I want to specify the base type of elements to be stored in MixVec.
  2. I want to be able to add data of that subtype in the insert method.
  3. I want to be able to specify only the type of the subtype in select_by_type.

PS, I had no idea about filter_map. This looks like a good shortcut!

Rust has type erasure and dynamic dispatch (via dyn Trait), but it does not have trait-based subtyping. A type that implements Trait is not a subtype of dyn Trait (except dyn Trait itself).

dyn Trait is a concrete type, even though it does dynamic dispatch and is dynamically sized (does not implement Sized).


dyn Any does support downcasting, but it's still not subtyping. Quoting the documentation:

A trait to emulate dynamic typing.

All those downcast, downcast_ref [1] methods on dyn Any and Box<dyn Any> [2] are how you perform the downcasting. Note that those are methods directly on the concrete types dyn Any and Box<dyn Any>, and are not part of the Any trait itself.

Rust doesn't have dyn Trait upcasting to super traits yet, but it plans to. Until then you have to explicitly program it in (example below).


In the playground below I added a trait with a supertrait bound on Any. That means that everything that implements MyTrait must also implement Any, and when you know that T: MyTrait, you also know that T: Any.

pub trait MyTrait: Any {
    fn as_any(&self) -> &dyn Any;
}

The as_any method is for upcasting, as mentioned above. This allows you to get a &dyn Any to the implementing type, so you can perform downcasting using the methods on dyn Any. You could add a downcast method to the trait itself too if you wanted.

Then you can store Box<dyn MyTrait> and use T: MyTrait bounds elsewhere.

You may want to use Box<dyn MyTrait + Send + Sync> or similar instead if you plan to use threads.


  1. etc ↩︎

  2. etc ↩︎

3 Likes

Rust is not an object-oriented language:

  • It has no classes,
  • no implementation inheritance,
  • and no subtyping in the classical sense. (The only kind of subtyping is related to lifetimes and variance, and it's emphatically not what you are looking for.)

Furthermore, a trait is not a type. Therefore, no type can ever be a "subtype" of a trait. A type can implement a trait. There is also dyn Trait, which is a concrete type, distinct from all other types it is created from. Still no subtyping in sight.

If you want to restrict your elements to a given trait stricter than Any, you'll have to add that bound on the insert method. There's no way in Rust (as far as I know of) to be generic over a trait. You can only be generic over a type. There's simply no way to express that a collection should restrict its items to different traits specified at generic instantiation time.

So the only overall change I can suggest to my earlier code is to extend the signature of insert:

fn insert<T: Any + MyTrait>(&mut self, value: T)
3 Likes

On nightly, you can sort of emulate it by being generic over T:?Sized (representing a trait object in practice) and adding a CoerceUnsized<T> bound to insert. As far as I know, however, there are no plans to stabilize CoerceUnsized in anything like its current form.

2 Likes

You can get around the CoerceUnsized requirement by requiring traits to opt-in with an appropriate From implementation:

impl<T:MyTrait> From<T> for Box<dyn MyTrait> {
    fn from(x:T)->Self { Box::new(x) }
}

// Some details omitted; see playground link

pub struct DynVec<T:?Sized=dyn Any>(Vec<Box<T>>);

impl<T:?Sized> DynVec<T> {
    fn new()->Self {
        DynVec(vec![])
    }
    
    fn push(&mut self, v:impl Into<Box<T>>) {
        self.0.push(v.into());
    }
    
    fn pop(&mut self)->Option<Box<T>> {
        self.0.pop()
    }
    
    fn pop_downcast<U:Any>(&mut self)->Option<U> where T:AsAny {
        let last: &T = self.0.last()?;
        if last.any_ref().is::<U>() {
            let x: Box<dyn Any> = self.pop().unwrap().any_boxed();
            assert!((*x).is::<U>());
            Some(*x.downcast().unwrap())
        } else {
            None
        }
    }
}
1 Like

as_any is probably a method I will never come up with on my own.
I'm glad I asked the question. Thank you.

Thanks for the useful advice.
Especially "a trait is not a type" was a shock to me.
I see, so I can't directly specify it as a type parameter....
Apparently my mind is still dominated by classes...

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.