Generic that returns Box<dyn A> or Arc<dyn A>

Is there a way to write the below where f is a generic that can return either a Box or Arc?

use std::sync::Arc;

trait A {}

struct A1 {}

impl A for A1 {}


fn f(i: &dyn A) -> Box<dyn A> {
    return Box::new(A1 {})
}

fn main() {
    let a = A1 {};
    let a = &a as &dyn A;

    let boxed: Box<dyn A> = f(a);
    let arced: Arc<dyn A> = f(a);
}

I don't think there's currently a direct way, especially since it involves coercing to the unsized type. You can use Into to convert the Box to Arc, f(a).into(), but that does internally move to a new heap allocation so it can store the counters with the value.

Got it. that is what I am doing atm: offer the most generic version (Box), and ask users to write .into() when they want Arc, but like you wrote, that does introduce a level of indirection.

Shouldn't the following work? I reckon there's impl<T: ?Sized> From<Box<T>> for Arc<T>:

fn f<T>(i: &dyn A) -> T
    where
        T: From<Box<dyn A>>
{
    return T::from(Box::new(A1 {}) as Box<dyn A>)
}

I.e., this moves the conversion into the function, so your users won't have to type .into() all the time.

@H2CO3 yes, you can do that, but it still has to allocate twice to arrive at Arc. Also, the ergonomic tradeoff is not an obvious win -- you avoid into(), but you introduce ambiguity to type inference.

    // these will get `T` from the explicit type annotation
    let boxed: Box<dyn A> = f(a);
    let arced: Arc<dyn A> = f(a);

    // this will be ambiguous
    let dunno = f(a);

That's true, although there was no single-allocation criterion in OP's question.

But that's exactly what OP's sample code shows, the expected invocation is such that the type is specified explicitly, isn't it?

Sure, if you always want that to be explicit, that's fine. But if you leave that into() conversion outside, then the cheaper Box case can be implicit.

1 Like

I don't think it's possible to cast them cheaply, because memory layouts of Box and Arc are different, so it has to have two versions of the code that build two different types in their own ways.

If we forget about the dyn part, and just imagine it being some Sized type:

trait HeapAllocatedPtr<T> {
    fn new (value: T) -> Self;
}

impl<T> HeapAllocatedPtr<T> for Box<T> {
    fn new (value: T)
      -> Box<T>
    {
        Box::new(value)
    }
}

impl<T> HeapAllocatedPtr<T> for Arc<T> {
    fn new (value: T)
      -> Arc<T>
    {
        Arc::new(value)
    }
}

and then write:

struct A1 {}
fn new_a1<PtrA1 : HeapAllocatedPtr<A1>> ()
  -> PtrA1
{
    PtrA1::new(A1 {})
}

so that

let _: Box<A1> = new_a1();
let _: Arc<A1> = new_a1();

Just Works™.

  • Although at the cost of always requiring an explicit type annotation to tell Rust which pointer to pick (there is no "type fallback" mechanism on functions yet).

The difficult part is actually the A1 -> dyn A part behind a pointer.

  • Either the trait above is rewritten with an explicit A1 and dyn A parameters,
    and then the f() function would have a P : HeapAllocatedPtr<A1, dyn A> kind of bound (which thus breaks abstraction and is not a good solution),

  • Or we commit to nightly to have a way to be generic over types coercible to a generic trait object. The nightly requirement is not great either.

With the latter approach, the following works on nightly Rust:

#![feature(unsize)]
use ::std::marker::Unsize;

trait HeapAllocatedPointer<DynTrait : ?Sized> {
    fn new<T : Unsize<DynTrait>> (value: T)
      -> Self
    ;
}

/* impls … */

trait A {}

fn f<Ptr> ()
  -> Ptr
where
    Ptr : HeapAllocatedPointer<dyn A>,
{
    Ptr::new({
        struct A1;
        impl A for A1 {}
        A1
    })
}

fn main ()
{
    let _: Box<dyn A> = f();
    let _: Arc<dyn A> = f();
}
4 Likes

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.