Как получить изменяемые ссылки на два элемента массива одновременно?

fn change(a: &mut i32, b: &mut i32) {
    let c = *a;
    *a = *b;
    *b = c;
}

fn main() {
    let mut v = vec![1, 2, 3];
    change(&mut v[0], &mut v[1]);
}

Когда я компилирую код выше, он имеет ошибку:

error[E0499]: cannot borrow `v` as mutable more than once at a time
 --> src/main.rs:9:32
  |
9 |         change(&mut v[0], &mut v[1]);
  |                     -          ^   - first borrow ends here
  |                     |          |
  |                     |          second mutable borrow occurs here
  |                     first mutable borrow occurs here

Почему компилятор запрещает это? v[0] а также v[1] занимают разные позиции в памяти, поэтому использовать их вместе не опасно. И что мне делать, если я сталкиваюсь с этой проблемой?

9 ответов

Решение

Вы можете решить это с split_at_mut():

let mut v = vec![1, 2, 3];
let (a, b) = v.split_at_mut(1);   // Returns (&mut [1], &mut [2, 3])
change(&mut a[0], &mut b[0]); 

Существует бесчисленное множество безопасных вещей, которые компилятор, к сожалению, пока не распознает. split_at_mut() просто так, безопасная абстракция, реализованная с unsafe заблокировать внутри.

Мы можем сделать это тоже для этой проблемы. Следующее - это то, что я использую в коде, где мне все равно нужно разделить все три случая (I: Индекс за пределами границ, II: Индексы равны, III: Отдельные индексы).

enum Pair<T> {
    Both(T, T),
    One(T),
    None,
}

fn index_twice<T>(slc: &mut [T], a: usize, b: usize) -> Pair<&mut T> {
    if a == b {
        slc.get_mut(a).map_or(Pair::None, Pair::One)
    } else {
        if a >= slc.len() || b >= slc.len() {
            Pair::None
        } else {
            // safe because a, b are in bounds and distinct
            unsafe {
                let ar = &mut *(slc.get_unchecked_mut(a) as *mut _);
                let br = &mut *(slc.get_unchecked_mut(b) as *mut _);
                Pair::Both(ar, br)
            }
        }
    }
}

На ночном канале сопоставление с образцом может быть выполнено с помощью срезов. Вы можете использовать это, пока у вас нет огромных индексов, и ваши индексы известны во время компиляции.

#![feature(slice_patterns)]

fn change(a: &mut i32, b: &mut i32) {
    let c = *a;
    *a = *b;
    *b = c;
}

fn main() {
    let mut arr = [5, 6, 7, 8];
    {
        let &mut [ref mut a, _, ref mut b, _..] = &mut arr;
        change(a, b);
    }
    assert_eq!(arr, [7, 6, 5, 8]);
}

Обратите внимание, что вам нужно включить функцию slice_patterns,

Правила заимствования в Rust необходимо проверять во время компиляции, поэтому что-то вроде заимствованного заимствования части Vec это очень сложная проблема (если не невозможная), и почему это невозможно с Rust.

Таким образом, когда вы делаете что-то вроде &mut v[i] Он будет занимать весь вектор.

Представь, что я сделал что-то вроде

let guard = something(&mut v[i]);
do_something_else(&mut v[j]);
guard.do_job();

Здесь я создаю объект guard внутри хранится изменяемая ссылка на v[i] и сделаю что-нибудь с этим, когда я позвоню do_job(),

В то же время я сделал то, что изменилось v[j], guard содержит изменяемую ссылку, которая должна гарантировать, что ничто другое не может изменить v[i], В этом случае все хорошо, пока i отличается от j; если два значения равны, это огромное нарушение правил заимствования.

Поскольку компилятор не может гарантировать, что i != j Таким образом, это запрещено.

Это был простой пример, но подобные случаи - легионы, и поэтому такой доступ изменчиво занимает весь контейнер. Плюс тот факт, что компилятор на самом деле недостаточно знает о внутренностях Vec чтобы гарантировать, что эта операция безопасна, даже если i != j,


В вашем конкретном случае вы можете взглянуть на swap(..) метод доступен на Vec это делает обмен, который вы выполняете вручную.

В более общем случае вам, вероятно, понадобится другой контейнер. Возможности оборачивают все ценности вашего Vec в тип с внутренней изменчивостью, такой как Cell или же RefCell или даже используя совершенно другой контейнер, как @llogiq предложил в своем ответе par-vec,

Метод [T]::iter_mut() возвращает итератор, который может дать изменяемую ссылку для каждого элемента в срезе. Другие коллекции имеют iter_mut метод тоже. Эти методы часто инкапсулируют небезопасный код, но их интерфейс абсолютно безопасен.

Вот черта расширения общего назначения, которая добавляет метод на кусочки, который возвращает изменяемые ссылки на два различных элемента по индексу:

pub trait SliceExt {
    type Item;

    fn get_two_mut(&mut self, index0: usize, index1: usize) -> (&mut Self::Item, &mut Self::Item);
}

impl<T> SliceExt for [T] {
    type Item = T;

    fn get_two_mut(&mut self, index0: usize, index1: usize) -> (&mut Self::Item, &mut Self::Item) {
        match index0.cmp(&index1) {
            Ordering::Less => {
                let mut iter = self.iter_mut();
                let item0 = iter.nth(index0).unwrap();
                let item1 = iter.nth(index1 - index0 - 1).unwrap();
                (item0, item1)
            }
            Ordering::Equal => panic!("[T]::get_two_mut(): received same index twice ({})", index0),
            Ordering::Greater => {
                let mut iter = self.iter_mut();
                let item1 = iter.nth(index1).unwrap();
                let item0 = iter.nth(index0 - index1 - 1).unwrap();
                (item0, item1)
            }
        }
    }
}

В недавних ночныхget_many_mut():

      #![feature(get_many_mut)]

fn main() {
    let mut v = vec![1, 2, 3];
    let [a, b] = v
        .get_many_mut([0, 1])
        .expect("out of bounds or overlapping indices");
    change(a, b);
}

Вы не можете сделать две изменяемые ссылки на одни и те же данные. Это что-то явно запрещенное средством проверки заимствования, чтобы предотвратить одновременные изменения. Однако вы можете обойти проверку заемщика с помощью unsafe блоки.

Хотя в вашем случае v[0] а также v[1] явно отдельные куски, которые не выдерживают серьезного изучения. Что, если v это какая-то карта под названием NullMap что отображает все элементы в одном поле? Как компилятор узнает в Vec операции v[0];v[1]; безопасно, но в NullMap нет?


Если вы пытаетесь обменять два элемента массива, почему бы не пойти на slice::swap?

fn main() {
    let mut v = vec![1, 2, 3];
    v.swap(0,1);
    println!("{:?}",v);
}

Также v должно быть mut, потому что вы меняете вектор. Неизменная версия клонировалась и выполняла обмен.

На основе ответа @bluss вы можете использоватьsplit_at_mut()чтобы создать функцию, которая может превратить изменяемое заимствование вектора в вектор изменяемых заимствований векторных элементов:

          fn borrow_mut_elementwise<'a, T>(v:&'a mut Vec<T>) -> Vec<&'a mut T> {
        let mut result:Vec<&mut T> = Vec::new();
        let mut current: &mut [T];
        let mut rest = &mut v[..];
        while rest.len() > 0 {
            (current, rest) = rest.split_at_mut(1);
            result.push(&mut current[0]);
        }
        result
    }

Затем вы можете использовать его, чтобы получить привязку, которая позволяет вам одновременно изменять множество элементов исходного Vec, даже когда вы выполняете итерацию по ним (если вы обращаетесь к ним по индексу в своем цикле, а не через какой-либо итератор):

          let mut items = vec![1,2,3];
    let mut items_mut = borrow_mut_elementwise(&mut items);
    for i in 1..items_mut.len() {
        *items_mut[i-1] = *items_mut[i];
    }
    println!("{:?}", items); // [2, 3, 3]

Я публикую свои ежедневные утилиты для этого на crate.io. Ссылка на документ .

Вы можете использовать его как

      use arref::array_mut_ref;
let mut arr = vec![1, 2, 3, 4];
let (a, b) = array_mut_ref!(&mut arr, [1, 2]);
assert_eq!(*a, 2);
assert_eq!(*b, 3);
let (a, b, c) = array_mut_ref!(&mut arr, [1, 2, 0]);
assert_eq!(*c, 1);

// ⚠️ The following code will panic. Because we borrow the same element twice.
// let (a, b) = array_mut_ref!(&mut arr, [1, 1]);

Это простая оболочка вокруг следующего кода, который звучит правильно. Но для этого требуется, чтобы два индекса различались во время выполнения.

      pub fn array_mut_ref<T>(arr: &mut [T], a0: usize, a1: usize) -> (&mut T, &mut T) {
    assert!(a0 != a1);
    // SAFETY: this is safe because we know a0 != a1
    unsafe {
        (
            &mut *(&mut arr[a0] as *mut _),
            &mut *(&mut arr[a1] as *mut _),
        )
    }
}

В качестве альтернативы вы можете использовать метод, который не будет паниковать сmut_twice

      #[inline]
pub fn mut_twice<T>(arr: &mut [T], a0: usize, a1: usize) -> Result<(&mut T, &mut T), &mut T> {
    if a0 == a1 {
        Err(&mut arr[a0])
    } else {
        unsafe {
            Ok((
                &mut *(&mut arr[a0] as *mut _),
                &mut *(&mut arr[a1] as *mut _),
            ))
        }
    }
}

Проблема в том, что &mut v[…] сначала покорно занимает v и затем дает непостоянную ссылку на элемент функции изменения.

Этот комментарий Reddit имеет решение вашей проблемы.

Редактировать: Спасибо за хедз-ап, Шепмастер. par-vec - это библиотека, которая позволяет нестандартно заимствовать разделенные разделы vec.

Другие вопросы по тегам