Expose a way to create an Arc fallibly

I'm working on a project in a no_std context that needs to be very careful with allocations and handle errors from the allocator when it's out of memory. We have a number of places that use Arc<T> and there doesn't currently appear to be a way of creating a new Arc<T> and handling an allocation failure that doesn't rely on unwinding and using catch_unwind. I would like to add one.

We have the ability to create a Box<T> like this:

pub fn try_box<T: Sized>(x: T) -> Result<Box<T>, OOM> {
    unsafe {
        let layout = Layout::new::<T>();
        let ptr = crate::alloc::alloc::alloc(layout);
        if ptr.is_null() {
            Err(OOM)
        } else {
            core::ptr::write::<T>(ptr as *mut T, x);
            Ok(Box::from_raw(ptr as *mut T))
        }
    }
}

But this approach does not work for Arc<T> because the creation of an Arc<T> allocates an ArcInner<T> and ArcInner<T> is a private type.

There seems to be two ways of providing this functionality and I would be willing to do the legwork for implementing either but wanted to receive feedback before heading down a wrong path.

  1. Expose an interface for creating an ArcInner<T> and to create an Arc<T> from a Box<ArcInner<T>> It could look something like this:
#[repr(C)]
pub struct ArcInner<T: ?Sized> {
    strong: atomic::AtomicUsize,
    weak: atomic::AtomicUsize,
    data: T,
}

impl<T: ?Sized> ArcInner<T> {
  pub fn new(data: T) -> Self {
    ArcInner {
        strong: atomic::AtomicUsize::new(1),
        weak: atomic::AtomicUsize::new(1),
        data,
    }
  }
}

impl<T: ?Sized> Arc<T> {
  // find a better name
  pub fn from_inner(inner: Box<ArcInner>) -> Self {
    Self::from_ptr(Box::into_raw(inner))
  }
}
  1. provide a try_new function that returns an Err on allocation failure.
imp<T: ?Sized> Arc<T> {
  pub fn try_new(data: T) -> Result<Self, TryNewError> {
    ...
  }
}

Do either of thees approaches seem acceptable? Are there other alternatives I've overlooked? Any feedback appreciated.

1 Like

ArcInner is already #[repr(C)], so "all" that would need to be done is stabilizing the heap representation of an Arc (and also that it's allocated with the global allocator).

Whatever is done for Arc should equivalently be done for Rc.


I've raised this possibility before (as I'm manually allocating Boxes for custom DSTs, and would like to do so for (A)Rc without having to go through a Box first), but so far haven't found the motivation to motivate it being exposed as such.

When you say "all that would need to be done is stabilizing the heap representation of an Arc". Do mean simply documenting it as such so that other crates could define an equivalent #[repr(C)] struct and be assured it will match the stdlib implementation going forward?

Yes. Once that heap representation is stable (as well as the scheme for the "phantom Weak", I suppose), downstream crates would be able to allocate the described structure and use from_raw to get the actual Arc.

While it could be done without providing ArcInner as a public type, the easiest way to define the heap representation would be to make the type public, so /shrug

Yeah, as I was thinking through the options it it seemed like declaring the type as stable meant it might as well be made public just to give it a name if nothing else. Then the logical next step after making the type public was asking does it really need to be stable? I was thinking that if the type becomes public then it doesn't really need to be a stable representation anymore. So long as users have the ability to get a correct Layout for the allocation and a method for creating an Arc from a Box'd version of the layout then the representation could still change going forward. That was my option 1 above.

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