Any Such Thing as an Owned Mutable Slice?

@leudz This is related to Shipyard scripting support :wink:

I've got a use-case where I want to be able to store byte "arrays" in a Vec where the byte arrays might be of any length that can only be determined at runtime. This playground shows my attempt at a wrapper around Vec that for this use pattern.

#[derive(Debug)]
struct DynamicVec<T> {
    storage: Vec<T>,
    element_size: usize
}

impl<T: Copy> DynamicVec<T> {
    fn new(element_size: usize) -> Self {
        Self {
            storage: Vec::new(),
            element_size,
        }
    }

    fn push(&mut self, data: &[T]) {
        debug_assert!(data.len() == self.element_size);
        
        self.storage.extend_from_slice(data);
    }
    
    // TODO: Implement other functions: pop, etc.
}

impl<T: Copy> std::ops::Index<usize> for DynamicVec<T> {
    type Output = [T];

    fn index(&self, index: usize) -> &Self::Output {
        &self.storage[index*self.element_size..index*self.element_size+self.element_size]
    }
}

What I'm trying to avoid, though is the Copy bound, which is required to do extend_from_slice.

I found an unsafe way around the copying, but the problem is that it is unsafe because fills data with uninitialized memory:


unsafe fn push(&mut self, mut data: &mut [T]) {
        debug_assert!(data.len() == self.element_size);
        
        let old_len = self.storage.len();
        self.storage.reserve(self.element_size);
        self.storage.set_len(old_len + self.element_size);
        let (left, right) = self.storage.split_at_mut(old_len);
        
        right.swap_with_slice(&mut data);
    }

The data argument needs to be a slice because I can't know the size of it at compile time, but logically I want to take ownership of data so that the user can't attempt to use it after pushing it. Is there any way to do that?

No, there are quite a few rfcs to implement &move but none of them went anywhere. For now you could use https://crates.io/crates/refmove

2 Likes

I would rewrite your code to this so that you don't put uninitialized memory behind a &mut T (which is likely UB, but that's under deliberation in UCG)

unsafe fn push(&mut self, mut data: &mut [T]) {
    let old_len = self.storage.len();
    self.storage.reserve(data.len());
    self.storage.as_mut_ptr().add(old_len)
        .copy_nonoverlapping(data.as_ptr(), data.len());
    self.storage.set_len(old_len + data.len());
}
1 Like

Oh, cool, thanks. :slight_smile:

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.