Object safety of composed receiver types

I'm having some trouble understanding the bounds of object safety: why is self: Arc<Self> an object safe receiver, but self: &Arc<Self> is not?

Because Arc<Self> has one indirection where &Arc<Self> has two. This means that you can trivially cast from Arc<Concrete> to Arc<Dynamic> but you can't cast from &Arc<Concrete> to &Arc<Dynamic>

Okay, I understand that if struct S implements trait T that I can cast Arc<S> to Arc<dyn T> but not &Arc<S> to &Arc<dyn T>.

But the conversion that has to happen in dynamic dispatch goes the other way: if a is an Arc<dyn T>, then for a.foo() to dispatch to an impl with the signature fn foo(self: Arc<S>) requires a cast from Arc<dyn T> to Arc<S>.

So what you say is correct, but I don't think it's an explanation.

The same reasoning applies. We can't construct a &Arc<Concrete> from a &Arc<Dynamic>. (for example, std::mem::size_of::<Arc<Concrete>>() != std::mem::size_of::<Arc<Dynamic>>()).

i.e. please implement this function, (I use [i32] here for example, but the same reasoning applies to trait objects)

// you may assume that the slice has the proper length.
unsafe fn cast(arc_ref: &Arc<[i32]>) -> &Arc<[i32; 10]> {
    todo!()
}

You may use any sort of unsafe wizardry that you desire, but the returned reference must be valid, and you can't rely on the layout of Arc being [ptr, len].

Here is an example implementation for Arc<[i32]>,

// you may assume that the slice has the proper length.
unsafe fn cast(arc: Arc<[i32]>) -> Arc<[i32; 10]> {
    Arc::from_raw(Arc::into_raw(arc) as *const [i32; 10])
}

We can simplify this further to just references if that makes it easier to implement.

// you may assume that the slice has the proper length.
unsafe fn cast<'a>(arc_ref: &&'a [i32]) -> &'a [i32; 10] {
    todo!()
}

hint hint: you can't implement the cast function when you have double indirection.

2 Likes

Thanks for taking the time to explain it.

It seems strange at first that if I have a: &Arc<dyn T>, that I have to do a.clone().foo()---make a clone that lasts the duration of the function call. I was thinking that an implementation of composed receivers could implement a: &Arc<Self> by essentially making that clone behind the scenes on the stack and passing a reference to it. I finally realize that this would not be a valid dispatch because the lifetime of the &Arc<S> would be different from the lifetime of the &Arc<dyn T>.

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