I've been refactoring some code at work where we use a fair amount of Arc<dyn Trait> for high-level components and ran into something that I thought should work.
The scenario roughly boils down to this:
use std::sync::Arc;
struct Foo {
bar: Arc<Bar>,
}
impl Foo {
fn debug_field(&self) -> &Arc<dyn std::fmt::Debug> {
&self.bar as &Arc<dyn std::fmt::Debug>
}
}
#[derive(Debug)]
struct Bar {}
error[E0605]: non-primitive cast: `&Arc<Bar>` as `&Arc<dyn Debug>`
--> src/lib.rs:9:9
|
9 | &self.bar as &Arc<dyn std::fmt::Debug>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ an `as` expression can only be used to convert between primitive types or to coerce to a specific trait object
For more information about this error, try `rustc --explain E0605`.
My instinct says no, but are there any ways to make this work besides either storing an Arc<Dyn Debug> as the bar field and losing the ability to access the concrete type, or modifying the method to return an owned Arc<dyn Debug> and requiring a reference count bump on each access?
I believe the idea behind returning a &Arc<dyn Trait> is so that you have the option to clone the Arc and hang onto the object for longer if you need to.
In the real code, these are fairly heavy weight objects that are useful in their own right, like an abstraction over an async executor, the networking layer, caching, and so on. You could imagine one part of the code using the reference directly in parent.executor().block_on(some_future) (where executor() returns a &Arc<dyn Async Runtime>), while another part of the code clones a handle to your networking layer and sends it to a background thread to perform some operation.
Hmm how about impl Trait as return type then? Unlike trait objects, this'd allow you add any bounds you like (like AsyncRuntime + Clone) to the concrete type (&Arc<Bar>) returned by your function:
use std::sync::Arc;
struct Foo {
bar: Arc<Bar>,
}
impl Foo {
fn debug_field<'a>(&'a self) -> impl std::fmt::Debug + Clone + 'a {
&self.bar
}
}
#[derive(Debug)]
struct Bar {}
I just tested the code above, the 'a bound is problematic as it does not allow you to clone out of 'a, creating a new Arc<Bar> that lives longer than the one owned by your Foo instance...