Как да разменям елементите на масив, срез или Vec?

Искам да разменя елементи на срез data с помощта на библиотечна функция, но не работи поради множество заеми:

use std::mem;

fn example() {
    let mut data = [1, 2, 3];
    let i = 0;
    let j = 1;
    
    mem::swap(&mut data[i], &mut data[j]);
}
error[E0499]: cannot borrow `data[_]` as mutable more than once at a time
 --> src/lib.rs:8:29
  |
8 |     mem::swap(&mut data[i], &mut data[j]);
  |     --------- ------------  ^^^^^^^^^^^^ second mutable borrow occurs here
  |     |         |
  |     |         first mutable borrow occurs here
  |     first borrow later used by call
  |

Може да се направи ръчно, но не мисля, че използването на този код всеки път е чудесно:

let temp = data[i];
data[i] = data[j];
data[j] = temp;

Има ли друго решение за размяна на елементи в срезове?


person Arsenii Fomin    schedule 03.02.2015    source източник
comment
Може да се направи ръчно, както обикновено ... на Copy типа - в противен случай това ще доведе до невъзможност за преместване от индексираното съдържание.   -  person Andrey Tyukin    schedule 10.01.2019


Отговори (1)


Има swap метод за срезове: data.swap(i, j).

Оригиналният код не работи, защото езикът изисква &muts да не създават псевдоними, тоест, ако дадена част от данните е достъпна чрез &mut, тогава не трябва да има друг начин за използване на тези данни. Като цяло, за последователни индекси data[i], data[j], компилаторът не може да гарантира, че i и j са различни. Ако те са еднакви, тогава индексирането се отнася до една и съща памет и така &mut data[i] и &mut data[j] ще бъдат два указателя към едни и същи данни: незаконно!

.swap използва част от unsafe код вътрешно, като се уверява, че обработва правилно случая i == j, избягвайки псевдонимите на &mut указатели. Това каза, че не трябва да използва unsafe, а само за да гарантира, че тази „примитивна“ операция има висока производителност (и определено мога да си представя бъдещи подобрения на езика/библиотеката, които намаляват нуждата от опасни тук, като прави изискваните инварианти по-лесни за изразяване), напр. следното е безопасно изпълнение:

use std::cmp::Ordering;
use std::mem;

fn swap<T>(x: &mut [T], i: usize, j: usize) {
    let (lo, hi) = match i.cmp(&j) {
        // no swapping necessary
        Ordering::Equal => return,

        // get the smallest and largest of the two indices
        Ordering::Less => (i, j),
        Ordering::Greater => (j, i),
    };

    let (init, tail) = x.split_at_mut(hi);
    mem::swap(&mut init[lo], &mut tail[0]);
}

Ключът тук е split_at_mut, който разделя среза на две несвързани половини (това се прави с помощта на unsafe вътрешно, но стандартната библиотека на Rust е изградена върху unsafe: езикът предоставя „примитивни“ функции и библиотеките изграждат останалото върху тях).

person huon    schedule 03.02.2015
comment
Вътре няма магия - просто опасен блок - колко жалко!) - person Arsenii Fomin; 03.02.2015
comment
Има в ptr mod: github.com /rust-lang/rust/blob/master/src/libcore/ptr.rs#L164 - person swizard; 03.02.2015
comment
@FominArseniy, може да се приложи с малко магия, за да се избегне unsafe (редактирах отговора си, за да включа примерна реализация). - person huon; 03.02.2015
comment
Може да се приложи и с помощта на временна променлива, не виждам защо има нужда от такава сложна реализация. Проблемът с двойното заемане възниква от размяната на синтаксиса на извикване на функция (mut & x, mut & y), когато x, y сочат към един и същи отрязък от данни. Ако синтаксисът на функцията е swap(& mut[], i, j), няма проблем да се размени вътре в нея с помощта на временна променлива. - person Arsenii Fomin; 03.02.2015
comment
@swizard, това е наистина драматична реализация на swap, но е версия на указател. - person Arsenii Fomin; 03.02.2015
comment
@FominArseniy, работи само с временна променлива, ако типът е Copy. Ако съдържащите се типове се движат (напр. data: &mut [String]), не е възможно да използвате кода, който давате във вашия въпрос, докато внимателният, който давам, работи. - person huon; 03.02.2015
comment
Мисля, че индексът &mut tail[hi] е грешен, не трябва ли да бъде &mut tail[0]? - person Matthieu M.; 03.02.2015
comment
@FominArseniy, да, това е версия на указател, която е процедурата за срез, внедрена чрез: github.com/rust-lang/rust/blob/master/src/libcore/slice.rs#L352 - person swizard; 03.02.2015