В небезопасном коде, правильно ли иметь несколько изменяемых ссылок (не указателей) на один и тот же массив, если они не используются для записи в одни и те же индексы?
Контекст
Я хотел бы получить несколько (различных) изменяемых представлений базового массива, которые я могу изменять из разных потоков.
Если непересекающиеся части являются смежными, это просто, простовызов split_at_mut
на срезе:
let mut v = [1, 2, 3, 4];
{
let (left, right) = v.split_at_mut(2);
left[0] = 5;
right[0] = 6;
}
assert!(v == [5, 2, 6, 4]);
Но я также хочу раскрыть несмежные непересекающиеся части.Для простоты, скажем, мы хотим получить изменяемое «представление» для четных индексов и другое изменяемое «представление» для нечетных индексов.
В отличие от split_at_mut()
, мы не смогли получить две изменяемые ссылки (нам нужнобезопасная абстракция!), поэтому мы используем вместо этого два экземпляра структуры, предоставляя только изменчивый доступ к четным (или нечетным) индексам:
let data = &mut [0i32; 11];
let (mut even, mut odd) = split_fields(data);
// …
С некоторым кодом небезопасным это легкочтобы получить такую безопасную абстракцию.Вот возможная реализация :
use std::marker::PhantomData;
struct SliceField<'a> {
ptr: *mut i32,
len: usize,
field: usize,
marker: PhantomData<&'a mut i32>,
}
impl SliceField<'_> {
fn inc(&mut self) {
unsafe {
for i in (self.field..self.len).step_by(2) {
*self.ptr.add(i) += 1;
}
}
}
fn dec(&mut self) {
unsafe {
for i in (self.field..self.len).step_by(2) {
*self.ptr.add(i) -= 1;
}
}
}
}
unsafe impl Send for SliceField<'_> {}
fn split_fields(array: &mut [i32]) -> (SliceField<'_>, SliceField<'_>) {
(
SliceField {
ptr: array.as_mut_ptr(),
len: array.len(),
field: 0,
marker: PhantomData,
},
SliceField {
ptr: array.as_mut_ptr(),
len: array.len(),
field: 1,
marker: PhantomData,
},
)
}
fn main() {
let data = &mut [0i32; 11];
{
let (mut even, mut odd) = split_fields(data);
rayon::join(|| even.inc(), || odd.dec());
}
// this prints [1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1]
println!("{:?}", data);
}
Пока все хорошо.
Проблема
Однако доступ к необработанному указателю далеко не удобен: вопреки слайсам, мы не можем использовать оператор []
или итераторы.
unsafe {
for i in (self.field..self.len).step_by(2) {
*self.ptr.add(i) += 1;
}
}
Очевидная идея заключается в локальном преобразовании необработанного указателя в фрагмент в небезопасной реализации:
let slice = unsafe { slice::from_raw_parts_mut(self.ptr, self.len) };
Тогда мы могли бы, например, переписать нашу реализацию в функциональном стиле:
slice.iter_mut().skip(self.field).step_by(2).for_each(|x| *x += 1);
Для этого примера это может не стоить, но для более сложного кода было бы гораздо удобнее вместо этого использовать кусочкинеобработанных указателей.
Вопрос
Это правильно?
Это явно нарушает правила заимствования : два потока могут одновременно содержать изменяемую ссылку ната же самая область памяти.Однако они могут никогда не записывать в одни и те же индексы.
Изменяемый псевдоним ссылки не указан как небезопасная сверхдержава , но список не представлен как исчерпывающий.