Casting `Arc<T>` to `Arc<dyn Trait>`

Hi, I'm having a bit of trouble with dynamic dispatch and the Sized trait.

The following code doesn't compile:

use std::sync::Arc;

pub trait Foo {
    fn bar(self: Arc<Self>) {
        self as Arc<dyn Foo>;
    }
}

with the error:

error[E0277]: the size for values of type `Self` cannot be known at compilation time
 --> src/lib.rs:5:9
  |
5 |         self as Arc<dyn Foo>;
  |         ^^^^ doesn't have a size known at compile-time
  |
  = note: required for the cast to the object type `dyn Foo`
help: consider further restricting `Self`
  |
4 |     fn bar(self: Arc<Self>) where Self: Sized {
  |                             +++++++++++++++++

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground` due to previous error

(Playground)

IIUC Arc<Self> should implement Sized regardless of whether or not Self implements Sized because it's a pointer. My only guess is that since Self isn't necessarily Sized, Arc<Self> could be a thin or fat pointer, and the compiler doesn't know how much memory to reserve. But this is based on my very limited knowledge of dynamic dispatch and seems unlikely.

1 Like

If Self is e.g. a slice, then unsizing to Arc<dyn Trait> cannot be performed.

4 Likes

Could you please explain why? I don't know much about dynamic dispatch.

This turned out to be an XY problem.

What I was trying to achieve is equivalent to:

use std::sync::Arc;

pub trait Foo {
    fn bar(self: Arc<Self>) {
        baz(self as Arc<dyn Foo>)
    }
}

fn baz(_: Arc<dyn Foo>) {
    todo!();
}

It has to be done like so:

use std::sync::Arc;

pub trait Foo {
    fn bar(self: Arc<Self>) {
        baz(self)
    }
}

fn baz<T>(_: Arc<T>)
where
    T: Foo + ?Sized,
{
    todo!();
}

The reason it isn't possible right now has to do with how slices and trait objects are implemented. They both use metadata next to a pointer to store the dynamic information they need to work with the unsized value.

When you have a slice &[T] that gets represented as a tuple with a pointer to the start of the slice and the length of the slice (*const T, usize) . When you have a trait object &dyn Foo that gets represented as a tuple with a pointer to the start of the data for the concrete type that implements the trait and a pointer to the vtable of the functions that implement Foo for the concrete type (*const T, *const VTable) [1].

So &dyn Foo is two pointers wide, but you need another pointer's worth of space to put the length of the slice in if you want a slice to be representable as a trait object. It's not really clear where that extra data should go.


  1. VTable isn't a real type ↩ī¸Ž

5 Likes

Is it possible to do something like this?

#![feature(unsize)]

use std::sync::Arc;

pub trait Foo: core::marker::Unsize<dyn Foo> {
    fn bar(self: Arc<Self>) {
        baz(self as Arc<dyn Foo>)
    }
}

fn baz(_: Arc<dyn Foo>) {
    todo!();
}

currently it results in the following error:

error[E0391]: cycle detected when computing the super predicates of `Foo`
 --> src/lib.rs:5:1
  |
5 | pub trait Foo: core::marker::Unsize<dyn Foo> {
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
note: ...which requires computing the super traits of `Foo`...
 --> src/lib.rs:5:1
  |
5 | pub trait Foo: core::marker::Unsize<dyn Foo> {
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  = note: ...which again requires computing the super predicates of `Foo`, completing the cycle
note: cycle used when collecting item types in top-level module
 --> src/lib.rs:5:1
  |
5 | / pub trait Foo: core::marker::Unsize<dyn Foo> {
6 | |     fn bar(self: Arc<Self>) {
7 | |         baz(self as Arc<dyn Foo>)
8 | |     }
9 | | }
  | |_^

For more information about this error, try `rustc --explain E0391`.

that kind of makes sense. (playground)

The other alternative is to define a function like follows:

use std::sync::Arc;

pub trait Foo {
    fn bar(self: Arc<Self>) {
        Self::dynamic(self);
    }
    
    fn dynamic(self: Arc<Self>) -> Arc<dyn Foo>;
}

struct A;

impl Foo for A {
    fn dynamic(self: Arc<Self>) -> Arc<dyn Foo> {
        self as Arc<dyn Foo>
    }
}

Which isn't ideal.

Just to mention, since both function return and function parameter are automatic coercion site you don't need the as Arc<dyn Foo> in every snippets in this thread.

Unsize<dyn Foo> + Sized is just Foo + Sized, provided Foo is dyn-safe.

You don't want that bound on your trait itself, as then dyn Foo (which is not Sized) wouldn't be able to implement Foo, and thus your entire trait is made not-dyn-safe.

You can put a Sized bound on individual methods.

You can make it work for unsized types with enough indirection.

4 Likes