Upcasting traits within non-smart pointer types (RwLock)

I need to clone a list of (specialized) trait objects into another list holding generalized trait objects.

To me this is a pretty simple/common use case but I fail to find a solution or even an acceptable workaround. Here's the problematic part for a single item:

use std::sync::Arc;
use std::sync::RwLock;

trait GeneralTrait {}
trait SpecializedTrait: GeneralTrait {}

impl GeneralTrait for () {}
impl SpecializedTrait for () {}

fn main() {
    let value: Arc<RwLock<()>> = Arc::new(RwLock::new(()));
    let _special: Arc<RwLock<dyn SpecializedTrait>> = value.clone() as Arc<RwLock<dyn SpecializedTrait>>;
    let _general: Arc<RwLock<dyn GeneralTrait>> = value.clone() as Arc<RwLock<dyn GeneralTrait>>;
    // Doesn't work because there's no way to convert SpecializedTrait into GeneralTrait
    let _upcasted: Arc<RwLock<dyn GeneralTrait>> = _special.clone() as Arc<RwLock<dyn GeneralTrait>>;
}

The common workarounds for this problem only work for smart pointers.

The only idea I had so far is to clone() every instance into a binding for each trait they implement (_special and _general in my example).

This is an unsolved problem, and is related to multi-trait trait object. A workaround is to do this.

trait Trait {}
trait Special: Trait {}

trait Ext: Special {
    fn to_trait(&self) -> &dyn Trait;
}

impl<T: Special> Ext for T {
    fn to_trait(&self) -> &dyn Trait;
}

But this only works for the finite about of smart-pointers that you implement this for.

https://github.com/rust-lang/rfcs/issues/2035

1 Like

Just to make sure: this doesn't work in my case because RwLock isn't a smart pointer type, right?

That is true, what are you trying to do?

Ok, thank you.
I'll have to get creative then :frowning:

I'll try to make this terse:
I have a tree-like structure. Each node may hold some resources (processables). It shall be possible to recursively clone-collect all those resources in a flat list.
Whoever collects those items is only interested in the process()-method. But the nodes need to have deeper knowledge of their items in order to configure them.

It can be compared to some GUI-tree: Window as root node > IconList > n * Icon
Icon would be a trait with a get_icon() method.
IconList contains those Icons in a Vec<Arc<RwLock<dyn Icon>>> and can call get_icon()
IconList and Icon both extend the trait Focusable
Every Node provides a get_focusables() -> Vec<Arc<RwLock<dyn Focusable>>> to recursively flatten all elements. The result would be: IconList, Icon[0], Icon[1], ...
I hope this is understandable.

My workaround would be to save each Icon in two lists within IconList:

  • children: Vec<Arc<RwLock<dyn Icon>>>
  • focusables: Vec<Arc<RwLock<dyn Focusable>>>

AFAIK, the layout of dyn Specialised is (potentially) different from a dyn General, hence you cannot (up)cast from within a RwLock<_> wrapper directly; you need a level of indirection: RwLock<Box<dyn _>> should therefore do it (untested), although with the outer Arc things might be a little hard for Rust.

  • I will try to toy with this later to hopefully show a working example.
1 Like

Here is a version that works :slight_smile:; it does however involve manually changing the vtable field, which depends on the unstable #[feature(raw)].

However, since your example only involved single trait objects, according to the UCG, we have:

so it should be doable in stable Rust.

The process of adding the vtable getter for each impl of the subTrait (and the declaration of the getter in the subTrait definition) could (and should) be made automatic with a proc_macro_attribute.

1 Like

Thanks a lot for your efforts but I think that workaround is too complex for me. My real world program uses over 15 traits and I cannot use nightly for production code.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.