`Arc<dyn T>` into `Box<dyn T>`?

I am trying to unwrap an Arc<dyn T> returned by a library, since I have no reason to keep the reference counting behavior, and I do know there's an Arc::try_unwrap but it seems it can't be used to unwrap DSTs without the unsized_locals feature.

It's not a huge deal, I know, since there's zero overhead in Arc::deref, I don't actually need mutable access, and unwrapping to a Box would incur the same overhead as the Arc::drop you're avoiding anyway..

There's no way to do this currently is there? Otherwise I'd consider discussing adding a method / TryFrom implementation for this in IRLO (except that above I've kinda convinced myself that I don't really need it anyway :laughing:)

I would be very surprised if it was possible to do this without allocating a whole new Box, because you raise the question, what happens to the refernce counts? I don't think it's feasible for Box<T> to "hide" a few extra words in the heap allocation even though they're not included in the T.

2 Likes

Oh, what I was implying was semantics similar to Arc::try_unwrap, where it first checks that there are no other strong or weak references before taking ownership. This proposed method would be exactly the same, but usable with dyn T instead of just T: Sized.

Edit: also, yes, I am saying it'd allocate a new Box and move the dyn T into it.

As a workaround, if you control the trait, you could add an Arc<Self> -> Box<dyn Trait> method to it.

3 Likes

Wouldn't you run into the same problem when writing the defaul impl for this method?

trait MyTrait : HelperMethod {
    /* Usual trait contents */
}

where:

trait HelperMethod {
    fn dyn_try_unwrap(self: Arc<Self>)
      -> Option<Box<dyn MyTrait>>
    ;
}

impl<T : MyTrait> HelperMethod for T {
    fn dyn_try_unwrap(self: Arc<Self>)
      -> Option<Box<dyn MyTrait>>
    {
        Arc::try_unwrap(self).map(|it| Box::new(it) as _)
    }
}

(feel free to slap + Send + Sync bounds in there as needed)

2 Likes

Right. The issue is I don't control the trait, or I'd just have the constructor return Box<dyn T> instead, as there is a impl<T: ?Sized> From<Box<T>> for Arc<T>..

I'm not sure this is possible without unsafe.

Here's a version with unsafe that leaks memory. It may also break other things too. So please don't use it :slight_smile:

Ha, yeah I was eyeing Arc::get_mut but I believe your solution is UB here:

let rv = Arc::get_mut(&mut arc).map(|p| {
    unsafe { Box::from_raw(p as *mut (dyn SomeTrait +'_)) }
});

I believe this is UB because Box::from_raw expects a pointer which originated from Box::into_raw. On top of that, you can also see the current implementation of Arc stores the T after the strong and weak counts in a #[repr(C)] struct, which means Box::drop would end up calling free on the middle of an allocation. This is all subject to change as implementation detail as well..

The closest I can get without UB (but with a leak) is this, with #[feature(set_ptr_value):

#![feature(set_ptr_value)]

extern crate alloc;

use core::mem;
use core::ptr;
use alloc::alloc::{alloc, Layout};
use alloc::sync::Arc;

fn to_box_leaky<T: ?Sized>(mut arc: Arc<T>) -> Option<Box<T>> {
    unsafe {
        let src: *mut T = Arc::get_mut(&mut arc)?;
        let dst = alloc(Layout::for_value(&*src));
        let len = mem::size_of_val(&*src);

        ptr::copy_nonoverlapping(src as *const T as *const u8, dst, len);

        mem::forget(arc);

        Some(Box::from_raw(src.set_ptr_value(dst)))
    }
}
1 Like

This is UB indeed, hidden by the fact that dropping Box<ZST> is a no-op. Adding a single field to the struct uncovers the problem - playground.

1 Like

Then indeed you would need unsafe or some lib that does it for you. But, as you noticed, it is currently not possible for non-stdlib-code to free the backing memory of an Arc without dropping the pointee when the pointee is unsized (FWIW, here is my approach if it wasn't for that caveat: it handles ZSTs and follows SB rules). This dooms your approach to be leaky, sadly.


What I'd suggest here instead, based on the OP, is to feature your own quasi-Box with the same semantics as Box, but for the drop glue. I was about to write one, but then I remembered @CAD97's ::rc-box crate, with a very interesting impl:

impl<T : ?Sized> TryFrom<Arc<T>> for ArcBox<T>

1 Like

Thanks for pointing that out!

Ooh wow, I totally didn't catch that Arc::into_raw returns a *const T! This solves my problem, and without reallocation, ty!

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.