[Unsafe Code Review Request] Is this unsafe code correct?


#1

I’m writing a library which requires two structs to have mutable access to different parts of the same Vec. The split I want is a not [0..n] and [n..], so I cannot use split_at_mut. I came up with the following reduced test case that I want to know is correct or not.

Essentially the returned structs mutably borrow the Vec without actually storing a mutable reference to it, using PhantomData. The 2 structs then never access the same memory locations – one accesses/modifies only even indices and the other only odd. Is this correct?

use std::marker;

struct Evens<'a, T: 'a> {
    ptr: *mut Vec<T>,
    marker: marker::PhantomData<&'a mut Vec<T>>,
}

struct Odds<'a, T: 'a> {
    ptr: *mut Vec<T>,
    marker: marker::PhantomData<&'a mut Vec<T>>,
}

impl<'a, T: 'a> Evens<'a, T> {
    fn get_mut(&mut self, index: usize) -> Option<&mut T> {
        if index % 2 == 0 {
            unsafe { (*self.ptr).get_mut(index) }
        } else {
            None
        }
    }
}

impl<'a, T: 'a> Odds<'a, T> {
    fn get_mut(&mut self, index: usize) -> Option<&mut T> {
        if index % 2 == 1 {
            unsafe { (*self.ptr).get_mut(index) }
        } else {
            None
        }
    }
}

fn split<'a, T>(vec: &'a mut Vec<T>) -> (Odds<'a, T>, Evens<'a, T>) {
    (Odds {
        ptr: vec as *mut _,
        marker: marker::PhantomData,
    },
     Evens {
        ptr: vec as *mut _,
        marker: marker::PhantomData,
    })
}

fn main() {
    let mut vec = vec![1, 2, 3];

    let (mut odds, mut evens) = split(&mut vec);

    assert_eq!(evens.get_mut(0), Some(&mut 1));
    assert_eq!(evens.get_mut(1), None);
    assert_eq!(odds.get_mut(0), None);
    assert_eq!(odds.get_mut(1), Some(&mut 2));

    // vec[1]; // cannot borrow `vec` as immutable because it is also borrowed as mutable
    {
        let _a = evens.get_mut(0);
        // let _b = evens.get_mut(0); // cannot borrow `evens` as mutable more than once at a time
    }
}

The library will only allow creating an instance of Evens and Odds through the split function.


#2

Any reason you made the marker on &'a Vec<T> instead of &'a mut Vec<T>? It works either way, but I think this has implications for variance. (although I quickly get lost on that stuff…)


#3

Thanks! that was an oversight. I’ve fixed it.

(I would also love to know if that could have triggered any Undefined Behavior.)