Search code examples
rust

Why is from_box_in(src: Box<T, A>) -> Rc<T, A> implemented this way in the rust std lib?


In Rusts standard library, Rc::from_box_in copies the memory, why not just do it like.

fn foo<T>(box_val: Box<T>) -> Rc<T> {
    let val_ptr = &*box_val as *const T;
    let rc_val: Rc<T> = unsafe { Rc::from_raw(val_ptr) };
    rc_val
}

I have not found any problem with this impl;


Solution

  • An RcBox<T> is not just T. In the current version on docs.rs:

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

    Note two counts (as cells) and the value.

    Further, note that Rc::from_raw calls Rc::from_raw_in, which subsequently does the following:

        pub unsafe fn from_raw_in(ptr: *const T, alloc: A) -> Self {
            let offset = unsafe { data_offset(ptr) };
    
            // Reverse the offset to find the original RcBox.
            let rc_ptr = unsafe { ptr.byte_sub(offset) as *mut RcBox<T> };
    
            unsafe { Self::from_ptr_in(rc_ptr, alloc) }
        }
    

    The original RcBox is found by pointer arithmetic. If that pointer wasn't pointing into the data field of an RcBox (and a raw pointer into a normal box does not point into the data field of an RcBox), then this is simply unsound, as it transmutes a wayward pointer into a pointer to an RcBox, then returns an Rc with that pointer. As soon as you use that Rc in any meaningful way, an invalid &RcBox-not-referencing-a-real-RcBox is produced.

    On the other hand, from_box_in (with the current implementation), does a bytewise copy, and then frees the original box allocation without calling drop glue on the contents, which is what we want. Essentially, it's manually performing something analogous to a rust move (which is just a bytewise copy) with the appropriate drop semantics.