Подтвердить что ты не робот

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

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] занимают разные позиции памяти, поэтому их не опасно использовать вместе. И что мне делать, если я столкнулся с этой проблемой?

4b9b3361

Ответ 1

Вы можете решить это с помощью 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)
            }
        }
    }
}

Ответ 2

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

Таким образом, когда вы делаете что-то вроде &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.

Ответ 3

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

#![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.

Ответ 4

Метод [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)
            }
        }
    }
}

Ответ 5

Вы не можете сделать две изменяемые ссылки на одни и те же данные. Это явно запрещается средством проверки заимствований, чтобы предотвратить одновременные изменения. Однако вы можете обойти проверку чеков с помощью блоков 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, потому что вы меняете вектор. Неизменяемая версия будет клонировать и выполнять обмен на ней.

Ответ 6

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

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

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