Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Safe API: Capture the lifetime of the original slice / allocation #11

Open
oberien opened this issue Mar 5, 2020 · 1 comment
Open

Comments

@oberien
Copy link
Owner

oberien commented Mar 5, 2020

@HeroicKatora stated in #8 (comment) and the following comments, that it might be possible to capture the lifetime of the original allocation and store with it the base-pointer and length. We can the check the base-pointer and length against the to-be-concatenated subsliced. The captured lifetime ensures that the allocation still exists, is valid and hasn't been changed. (playground

pub struct SameAllocation<'a, T> {
    phantom: PhantomData<&'a [T]>,
    base: usize,
    bytes: usize,
}

impl<'a, T> SameAllocation<'a, T> {
    pub fn new(ptr: &'a [T]) -> Self {
        SameAllocation {
            phantom: PhantomData,
            base: ptr.as_ptr() as usize,
            bytes: ptr.len() * mem::size_of::<T>(),
        }
    }

    // Pretend to borrow but return the complete array.
    pub fn new_mut(ptr: &'a mut [T]) -> (Self, &'a mut [T]) {
        let result = SameAllocation {
            phantom: PhantomData,
            base: ptr.as_ptr() as usize,
            bytes: ptr.len() * mem::size_of::<T>(),
        };
        (result, ptr)
    }

    // Possible `unsafe fn new_unchecked` variant omitted

    // Join two slices that must be part of the allocation used in the construction.
    pub fn join<'b: 'a>(&self, a: &'b mut [T], b: &'b mut [T]) -> Result<&'b mut [T], Error> {
        // Assert the beginning is within the allocation. Instead of asserting we could error.
        // This guarantees each complete slice is within the same allocation.
        assert!(self.base <= a.as_ptr() as usize && a.as_ptr() as usize <= self.base + self.bytes);
        assert!(self.base <= b.as_ptr() as usize && b.as_ptr() as usize <= self.base + self.bytes);
        unsafe {
            // SAFETY: Guaranteed the exact check we need for the current method.
            concat_slice_unchecked(a, b)
        }
    }
}

// usage:
fn main() {
    let mut data = [0xa; 16];
    let (allocation, data) = SameAllocation::new_mut(&mut data[..]);
    
    let (a, b) = data.split_at_mut(10);
    let rejoined = allocation.join(a, b).unwrap();
    
    assert_eq!(rejoined, &[0xa; 16]);
}
@HeroicKatora
Copy link
Collaborator

HeroicKatora commented Sep 10, 2020

A similar idea just goes into the std collections library: https://github.com/rust-lang/rust/pull/74437/files#diff-1669d4df3278e7aa0eb152f5d13bb141

A crucial difference is that the above code does not use the stored pointer as a reference so no other mutable borrows are invalidated. Thus the caller need not unsafely ensure that no such borrows exist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants