How to create a Rc<RefCell<[Value]>> (or convert to it from a Vec<Value>)?

I want to create a Rc<RefCell<[Value]>>. (a reference-counted shared pointer to an dynamically allocated fixed-size array living on the heap, whose length is determined at runtime when it is created, and that I can mutate). How can I create such an object? (either by creating one filled with a default value, or by creating one from a Vec)?

From<Vec<T>> is implemented for Box<[T]> and for Rc<[T]> so I can easily create those two types by calling .into(), but not Rc<RefCell<[T]>>

use std::rc::Rc;
use std::cell::RefCell;
fn main() {
    let a: Vec<i32> =vec![1; 32];
    
    //let b: Box<[i32]> = a.into(); // works
    
    //let b: Rc<[i32]> = a.into(); // works
    
    //let b: Rc<RefCell<[i32]>> = a.into(); // doesn't work, From trait not implemented

    // doesn't work, because a[..], with type [i32], is unsized
    let b: Rc<RefCell<[i32]>> = Rc::new(RefCell::new(a[..]));
}

(In the end, I created a Rc<[RefCell<Value>]> instead, but I'm still wondering how to do what I initially wanted.)

the slice type [Value] is unsized, it can be unsize-coerced from an array type. if the size of the array is not known at compile time, as a special case, you can create a boxed slice (or Rc of slice) from a Vec.

but RefCell<[Value]> doesn't have the special treatment of Box<[Value]> or Rc<[Value], it is created by cocersing from a RefCell<[Value; SIZE]> only.

1 Like

Coerce from an array after creating the Rc.

    let a = RefCell::new([1; 32]);
    let b: Rc<RefCell<[i32]>> = Rc::new(a) as _;

This only works because the [1; 32] has a fixed size that is known at compile time, right?

I want the size of my array (or "slice", I guess is the proper Rust terminology?) to be determined at runtime. (I updated my original post to make this more clear)

I found a way, highly unsafe, because it relies on the memory representation of RefCell that may change.
I'm curious about a safe way to do it.

#![feature(ptr_metadata)]

use std::{
    alloc::{Layout, alloc, handle_alloc_error},
    cell::RefCell,
    ptr::{self, from_raw_parts_mut},
    rc::Rc,
};

fn rc_refcell_from_vec<T>(mut vec: Vec<T>) -> Rc<RefCell<[T]>> {
    let len = vec.len();

    let (layout, offset) = Layout::new::<RefCell<[T; 0]>>()
        .extend(Layout::array::<T>(len).expect("array layout"))
        .expect("extend layout");
    let layout = layout.pad_to_align();

    unsafe {
        let raw = alloc(layout);
        if raw.is_null() {
            handle_alloc_error(layout);
        }

        let header: *mut RefCell<[T; 0]> = raw.cast();
        ptr::write(header, RefCell::new([]));

        let data: *mut T = raw.add(offset).cast();
        for (i, item) in vec.drain(..).enumerate() {
            data.add(i).write(item);
        }

        let wide: *mut RefCell<[T]> = from_raw_parts_mut(raw.cast::<()>(), len);

        Rc::from(Box::from_raw(wide))
    }
}

fn main() {
    let rc = rc_refcell_from_vec(vec![1, 2, 3]);
    rc.borrow_mut()[0] = 42;
    println!("{:?}", &*rc.borrow());
}

As Bruecki said, fat pointer layout is not guaranteed. I think the use of the ptr_metadata feature may solve the problem, but I'm not sure this is safe.

1 Like

You can use RefCell<[T; 0]> instead of Cell<isize> to make this less unsafe and more resilient to future changes to RefCell.

2 Likes

Your code has UB when you run it with Miri because you did not pad_to_align the layout :

error: Undefined Behavior: pointer not dereferenceable: pointer must be dereferenceable for 24 bytes, but got alloc475 which is only 20 bytes from the end of the allocation
    --> /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/boxed.rs:1272:9
     |
1272 |         Box(unsafe { Unique::new_unchecked(raw) }, alloc)
     |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
     |
     = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
     = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
help: alloc475 was allocated here:
    --> src/main.rs:11:19
     |
  11 |         let ptr = alloc(layout) as *mut Cell<isize>;
     |                   ^^^^^^^^^^^^^
     = note: BACKTRACE (of the first span):
     = note: inside `std::boxed::Box::<std::cell::RefCell<[i32]>>::from_raw_in` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/boxed.rs:1272:9: 1272:58
     = note: inside `std::boxed::Box::<std::cell::RefCell<[i32]>>::from_raw` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/boxed.rs:1045:18: 1045:48
note: inside `rc_refcell_from_vec::<i32>`
    --> src/main.rs:22:18
     |
  22 |         Rc::from(Box::from_raw(fat_ptr))

When you replace the Cell<isize> with RefCell<[T;0]> as Skifire13 suggested the only source of UB left is

let fat_ptr: *mut RefCell<[T]> = mem::transmute((ptr, len))

because fat pointer layout is not guaranteed (AFAIK).

1 Like

Thank you, I've added the pad_to_align. About the fat pointer layout, I've changed it using the ptr_metadata feature, but I'm not sure this is safer

1 Like

Considering that Box<T> is the standard way to represent an singly-owned object on the heap, and Rc<RefCell<T>> is the standard way to represent a multi-owned object on the heap (while maintaining mutability) could it make sense to add a function like this to the standard library?

impl<T: ?Sized> for Box<T> {
    fn into_rc_refcell(self) -> Rc<RefCell<T>> {...}
}
1 Like

there's a caveat though: convertion from Box to Rc is not a cheap conversion!

you cannot do a shallow move (of the heap pointer), you must first allocate memory for the Rc, initialize the strong and weak reference counts, then do a memcpy from the Box, and finally deallocate the memory of the Box without dropping the content.

unfortunately, the RcInner type is private to the standard library, it is impossible to allocate an Rc<Dst> manually, unlike Box, as you cannot calculate the Layout for an Rc.

although in general the layout of a pointer to DST is unstable, the standard library does provide a stable API to construct a slice pointer, as a special case.

in other words, ptr::from_raw_parts() is unstable, but ptr::slice_from_raw_parts() is stable.

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.