Do Box/Rc have special compiler support?

i'm implementing a custom version of Rc (without the logic for weak pointers & support for allocators).
but i'm having an issue, where i can't create an Rc<dyn Any> using Rc::new(42i32), it's giving me an Rc<i32> instead. it does work with rust's std::rc::Rc.

so the question is: does the rust compiler implement some magic to make this happen? and if not, what am i missing, some trait maybe? or how else could i make this work?

below, i've copy pasted all the relevant code from std to make TestTc::new(42i32) "compile".
but it doesn't work either, same issue as with my custom Rc.

use core::cell::Cell;
use core::marker::PhantomData;
use core::any::Any;

#[repr(C)]
struct RcBox<T: ?Sized> {
    strong: Cell<usize>,
    weak: Cell<usize>,
    value: T,
}

pub struct TestRc<T: ?Sized> {
    ptr: core::ptr::NonNull<RcBox<T>>,
    phantom: PhantomData<RcBox<T>>,
}

impl<T> TestRc<T> {
    unsafe fn from_inner(ptr: core::ptr::NonNull<RcBox<T>>) -> Self {
        Self { ptr, phantom: PhantomData }
    }

    #[cfg(not(no_global_oom_handling))]
    pub fn new(value: T) -> TestRc<T> {
        // There is an implicit weak pointer owned by all the strong
        // pointers, which ensures that the weak destructor never frees
        // the allocation while the strong destructor is running, even
        // if the weak pointer is stored inside the strong one.
        unsafe {
            Self::from_inner(
                Box::leak(Box::new(RcBox { strong: Cell::new(1), weak: Cell::new(1), value }))
                    .into(),
            )
        }
    }
}

fn main() {
    // error: expected `TestRc<dyn Any>`, got `TestRc<i32>`
    let foo: TestRc<dyn Any> = TestRc::new(42i32);
}

It's CoerceUnsized.

haha, that was quick, thanks!
sadly that requires nightly rust.

is there some way i could achieve this on stable?
perhaps not with the regular new function, but some other hack new_dyn, until CoerceUnsized becomes stable.

since there's no "trait polymorphism", i'm not sure what the signature would be.
i only need dyn Any for now, maybe i can make that work.

You can directly coerce sized types to unsized. Eg.:

struct S<T: ?Sized> {
    inner: T
}

fn main() {
    let s: Box<S<[u8]>> = Box::new(S { inner: [1, 2, 3] });
}

I don't know what you mean by that.

the signature of new_dyn would have to be something like this:

fn new_dyn<X: Trait, T: X>(value: T) -> TestRc<dyn X> { ... }

this seems like an acceptable temporary solution:

    pub fn new_any(value: T) -> TestRc<dyn Any> {
        let boks = Box::leak(Box::new(RcBox { strong: Cell::new(1), weak: Cell::new(1), value }));
        let boks: &mut RcBox<dyn Any> = boks;
        let boks: NonNull<RcBox<dyn Any>> = boks.into();
        TestRc { ptr: boks, phantom: PhantomData }
    }

No it wouldn't. dyn Trait is not the only kind of unsized type. That signature wouldn't work with slices, str, or custom DSTs.

That seems like a lot of unnecessary pointer gymnastics going through mutable references. That's usually UB because you end up accidentally aliasing stuff behind the &mut; if you are managing memory yourself, stick with raw pointers. You should do this instead:

pub fn new(value: T) -> TestRc<dyn Any> {
    let bx: Box<RcBox<dyn Any>> = Box::new(RcBox { strong: Cell::new(1), weak: Cell::new(1), value });
    let ptr = Box::into_raw(bx);
    let ptr = NonNull::new(ptr).unwrap();
    TestRc { ptr, phantom: PhantomData }
}
2 Likes

oh, right! i was just considering trait objects for now.

yeah, i just started with the stdlib thing and went "depth first".
the actual impl in my lib now looks like this:

    pub fn new_any_in(value: T, alloc: A) -> Rc<dyn Any, A>  where T: 'static {
        let inner: NonNull<RcInner<T, A>> = alloc.alloc(Layout::new::<RcInner<T, A>>()).unwrap().cast();
        unsafe {
            inner.as_ptr().write(RcInner {
                alloc,
                refs: Cell::new(1),
                data: value,
            });
        }
        let inner: NonNull<RcInner<dyn Any, A>> = inner;
        return Rc { inner };
    }

Note that Rc does have some special compiler support in regard to Rc<Self> as method receiver. See Special types and traits in the Rust reference. If I understand it right, then you can't do these things with your own Rc type.

3 Likes

That one is arbitrary_self_types. And perhaps DispatchFromDyn.

2 Likes

Yeah, DispatchFromDyn is the "extreme magic" trait in play here (when Rc<Self> where Self: !Sized).