Segfault when assigning Arc to dynamically sized type header

I have a dst that looks like this:

#[repr(C)]
pub struct RegionRepr {
    meta: RegionMeta,
    chunks: [ChunkRepr],
}

#[repr(C)]
struct RegionMeta {
    pub origin: IVec3,
    pub settings: Arc<WorldSettings>, // < this causes a problem
    pub state: RegionState,
    pub col_len: usize, 
    pub columns: [ColumnState; 256],
}

struct ChunkRepr {
     blocks: // custom data structure
}

I want to combine all the region data into one pointer allocation because the number of chunks is known at initialization time, and to reduce the number of pointer dereferences needed to access chunks. Its' a small optimization, but it will be called millions, or even billions of times so I care.

I am initializing an empty region like so, based on the implementation for Arc<[T]>:

impl RegionRepr {
    pub fn empty(origin: IVec3, settings: Arc<WorldSettings>) -> Box<Self> {
        // the length of the regions' chunks buffer.
        let len = 16 * 16 * settings.chunks_per_column();

        // compute the layout by combining the layouts of the fields.
        let layout = Layout::new::<RegionMeta>()
            .extend(Layout::array::<ChunkRepr>(len).unwrap())
            .unwrap().0.pad_to_align();

        let mut region: Box<RegionRepr> = unsafe {
            // allocate a pointer for chunks and meta
            let raw = ptr::NonNull::new(alloc(layout) as *mut ()).unwrap();

            // convert the thin pointer to a fat pointer (slice) and box it
            let ptr = NonNull::new(ptr::slice_from_raw_parts_mut(raw.as_ptr().cast::<ChunkRepr>(), len)).unwrap();
            Box::from_raw(ptr.as_ptr() as *mut _)
        };

        // Initialize region metadata
        region.meta = RegionMeta {
            origin,
            col_len: settings.chunks_per_column(),
            settings,
            state: RegionState::empty(),
            columns: [ColumnState::empty(); 256],
        };

        // initialize chunks
        for i in 0..len {
            region.chunks[i] = ChunkRepr::empty()
        }

        region
    }
}

However, this code results in a segmentation fault. I found that the segfault goes away and this code runs perfectly if I remove the Arc<WorldSettings> from the metadata. I've attached links to the playground with examples. Any idea why this occurs, and how I can solve it?

link to playground with Arc

link to playground without Arc

Looks like you're using uninitialized memory without following the rules described here:

OHHHHHHHHHHHHHHH its trying to drop the Arc isn't it

Good point, it assumes the region is initialized and contains an existing Arc, so overwriting it would drop the uninitialized Arc. This is why ptr::write is often used to initialize memory.

yep, initializing with ptr::write did it. Thanks for taking a look at this, it helps to have another pair of eyes on things :-).

You're welcome. I'm not an expert on this, but there is more to it than just using ptr::write. My understanding is that you can't soundly create a reference (as opposed to a raw pointer) to uninitialized memory. So you have to initialize it before creating the reference. If that will happen sometime later, wrapping it in MaybeUninit can be used to defer initialization, and after initialization you call MaybeUninit::assume_init to "unwrap" it. I suggest trying to follow the patterns in the examples for MaybeUninit.

Or perhaps the simplest approach is to fully initialize it using raw pointers, before creating a reference to it, and then you don't need MaybeUninit.

2 Likes