Generic type for a trait object function, à la the Any trait

Hello Everyone,

I'm trying to use exactly the same pattern that the Any trait uses for downcast_ref: i.e.

pub trait QWSElement {
    fn payload<T:Sized>(&self) -> Option<&T>;
}

But the generic is preventing it from being made into an object.

element : Box<dyn QWSElement>
|               QWSElement` cannot be made into an object
...because method `payload` has generic type parameters

Anyway there is a work-around, where I can cast my &dyn QWSElement to an &dyn Any, and then call Any's downcast_ref, but I'd like to expose a cleaner interface.

e.g.

element.payload::<i32>().unwrap()

instead of:

element.as_any().downcast_ref::<QWSElementWrapper<i32>>().unwrap().payload()

How does the Any trait make this work? Does the compiler just know to treat it as special, or is there some mechanism to allow generic types to work with trait objects in the general case?

Thank you.

Because the trait Any doesn't have a generic method downcast_ref<T>(&self) -> Option<&T>. It's a method of the type dyn Any.

The trait Any contains only single method fn type_id(&self) -> TypeId. Where the magic lives is in the impl<T: 'static + ?Sized> Any for T, which implement the type_id method with some compiler instrinsics, which is another name of the magic.

3 Likes

This trait is not object-safe for the reason stated in the error message. To make this trait object-safe, you have to limit generic methods to sized types like this:

pub trait QWSElement {
    fn payload<T:Sized>(&self) -> Option<&T>
    where
        Self: Sized;
}

T doesn't have to be sized to make this work, Self has to. T is actually already Sized by default. Only traits themselves are ?Sized by default.

I assume you do want to call the method, tho. In that case, you have to add another method to the trait:

pub trait QWSElement {
    // […]
    fn payload_dyn(&self) -> Option<&dyn Any>;
}

payload_dyn can be called from a trait object, because it doesn't have generic (non-lifetime) parameters. This is currently the only way to get what you want. Programming languages like Java have to do this, as well, but since anything is an object there, they never deal with the (Rust-)generic version you wrote, first.

1 Like

Thank you for fixing my broken thinking! This now works beautifully.

impl dyn QWSElement {
    pub fn get_payload<T : Any>(&self) -> Option<&T> 
    {
        self.as_any().downcast_ref::<QWSElementWrapper<T>>().map(|element_wrapper| element_wrapper.payload())
    }
}
1 Like

Are you on nightly? I don't recall impl dyn being a thing.

I guess I was on rustc 1.49.0-nightly (4760b8fb8 2020-10-25)

however I just rolled back to rustc 1.48.0 (7eac88abb 2020-11-16) and it still works.

1 Like

This has kinda morphed into a lifetimes question now. I thought I had a reasonable grasp on lifetimes at this point, but this situation has thrown me a curve ball.

For some reason, impl dyn seems to imply an 'static bound. For example,

impl dyn QWSElement {
    pub fn test_print(&self)
    {
        println!("test");
    }
}
...
impl QuantumWorldState {
    pub fn get_element(&self, element_id : QWSElementID) -> Option<&dyn QWSElement> {
}
...
let element = quantum_world_state.get_element(el_id).unwrap();
element.test_print();

causes the error

borrowed value does not live long enough
|       argument requires that `quantum_world_state` is borrowed for `'static`

Yet strangely, although dyn Any's downcast_ref follows the same pattern, this works fine:

let element = quantum_world_state.get_element(el_id).unwrap();
element.as_any().downcast_ref::<QWSElementWrapper<i32>>().do_something();

At this point, I can work around it by returning Option<&Box<dyn QWSElement>> instead of Option<&dyn QWSElement> from get_element(), so it's really more that there is something unclear and it is bothering me because it tells me that something deeper is amiss in my understanding.

Thank you both for the replies, and everyone else for reading this.

dyn Trait has a hidden lifetime parameter that defaults to 'static if you don’t explicitly specify it. You need something like this:


impl<'a> dyn QWSElement + 'a {
    pub fn test_print(&self)
    {
        println!("test");
    }
}
1 Like

Thank you!!! That fixed everything.

I wonder why impl dyn has that implicit default bound... :thinking: I can't seem to figure out the reason for it. Although I've learned that when the Rust core language does something it's seldom without a very good reason.

Thanks again for answering my question. I wonder if the Rust community is so awesome because of the language or if the language is so awesome because of the community? :thinking:

It's always good to ask, if you don't know the reason. As smart as the people who created Rust are, they are still humans and there are some things, that likely would've been done differently, if they did it from scratch, but can't due to stability guarantees. I don't know the answer to your question either.

I don't think there is a relation between the two. Striving for a welcoming community should be the goal of every programming language out there. Most programming languages don't even have official channels for the community to interact with each other. However, a great community does not fix design mistakes of any language and Rust also has a few of them and definitely much fewer than any other language I've worked with so far.

I guess, a well-designed language could make users happier, which might reflect onto the community. That's purely hypothetical, though.

1 Like

It’s really just dyn Trait that has the default bound. Because it’s used for type erasure, the normal lifetime inference can’t work: Objects with different embedded lifetimes could be stored in the same variable at runtime. To fix this, dyn Trait objects must have an explicitly-specified lifetime which is checked at the point of creation.

The most common case by far is handling objects that contain no internal references, so ’static is used by default if it’s not orherwise specified. I have no idea why the syntax for this parameter is dyn Trait + 'a instead of something more consistent with other lifetime parameters, like dyn<'a> Trait.

1 Like

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.