Почему я могу принудительно вызвать семантику перемещения ссылки для параметра метода & & self, а не для параметров функции? - PullRequest
0 голосов
/ 27 августа 2018

У меня есть две версии функции, предназначенные для одного и того же.

Версия 1 - Работает!

pub fn example1() {
    // Make a mutable slice
    let mut v = [0, 1, 2, 3];

    // Make a mutable reference to said slice
    let mut v_ref = &mut v[..];
    let len = v_ref.len();

    // Reduce slice to sub-slice -> np ;)
    v_ref = {
        // Create sub-slices
        let (v_l, v_r) = {
            // Involves some edits -> need mut
            v_ref.swap(0, len - 1);

            { v_ref }.split_at_mut(len / 2)
        };

        // Choose slice with which to overwrite
        // (involves some non-trivial condition here)
        match len % 2 {
            0 => v_l,
            _ => v_r,
        }
    };

    // And then we do something with v_ref
    println!("{:?}", v_ref);
}

По существу:

  • Мы начинаем с изменяемой переменной mut v_ref: &mut [i32], содержащей изменяющуюся ссылку на фрагмент
  • Мы делаем два среза из v_ref, используя split_at_mut*
  • Переменная v_ref перезаписывается для хранения одного из подрезов

* ( Примечание - Мы избегаем проблемы наличия двух изменяемых ссылок путем перемещения v_ref, в отличие от повторного заимствования с использованием блок идентификации )

(Что касается намерений кода - это сокращение фрагмента предназначено для повторения в цикле; однако эта деталь не влияет на проблему)

Версия 2 - не компилируется

Версия 2 почти идентична версии 1, , за исключением , что создание подреза перемещено в его собственную функцию:

fn example2_inner(v_ref: &mut [i32]) -> (&mut [i32], &mut [i32]) {
    // Recreate len variable in function scope
    let len = v_ref.len();

    // Same mutation here
    v_ref.swap(0, len - 1);

    // Same slice split here
    v_ref.split_at_mut(len / 2)
}

pub fn example2() {
    let mut v = [0, 1, 2, 3];

    let mut v_ref = &mut v[..];
    let len = v_ref.len();

    // This now fails to compile :(
    v_ref = {
        // Mutating & slice spliting moved to function
        let (v_l, v_r) = example2_inner({ v_ref });

        match len % 2 {
            0 => v_l,
            _ => v_r,
        }
    };

    println!("{:?}", v_ref);
}

Когда я пытаюсь построить это, я получаю следующие ошибки:

error[E0506]: cannot assign to `v_ref` because it is borrowed
  --> src/lib.rs:19:5
   |
19 | /     v_ref = {
20 | |         // Mutating & slice spliting moved to function
21 | |         let (v_l, v_r) = example2_inner({ v_ref });
   | |                                           ----- borrow of `v_ref` occurs here
22 | |
...  |
26 | |         }
27 | |     };
   | |_____^ assignment to borrowed `v_ref` occurs here

error[E0502]: cannot borrow `v_ref` as immutable because `*v_ref` is also borrowed as mutable
  --> src/lib.rs:29:22
   |
21 |         let (v_l, v_r) = example2_inner({ v_ref });
   |                                           ----- mutable borrow occurs here
...
29 |     println!("{:?}", v_ref);
   |                      ^^^^^ immutable borrow occurs here
30 | }
   | - mutable borrow ends here

Вопросы

  • Почему эти два примера не компилируются одинаково? Применяется ли изменяемая семантика перемещения ссылок (т. Е. {vref} из E1) для методов (т. Е. split_at_mut), но не для функций (т. Е. example2_inner)? Почему это так?
  • Я хотел бы сохранить example2_inner в качестве независимой функции полезности: как бы я изменил Пример 2, чтобы приспособиться к этому?

Ответы [ 2 ]

0 голосов
/ 28 августа 2018

Почему эти два примера не компилируются одинаково? Изменчивы семантика ссылочного перемещения (т. е. {vref} из E1), применяемая для методы (т. е. split_at_mut), но не функции (т. е. example2_inner)? Почему это так?

На самом деле речь идет не о методах против функций, а о синтаксисе вызова метода против синтаксиса вызова функции.

Каждый вызов метода может быть переведен в UFCS (универсальный синтаксис вызова функции), который обычно имеет такую ​​форму:

<Type as Trait>::method(args);

Если мы предпримем наивную попытку перевести вызов { v_ref }.split_at_mut(len / 2) в UFCS в версии 1, мы получим ту же ошибку, что и в версии 2:

<[_]>::split_at_mut({v_ref}, len / 2)

Причина в том, что вышесказанное эквивалентно чему-то, что опять же не приводит к перемещению v_ref в блок:

<[_]>::split_at_mut({&mut *v_ref}, len / 2)

Синтаксис метода в действительности разрешает следующий рабочий вариант:

<[_]>::split_at_mut(&mut *{v_ref}, len / 2)

Для этого варианта компилятор делает вывод, что v_ref действительно должен быть перемещен в блок, потому что компилятор понимает, что повторное заимствование, необходимое для вызова метода, уже должно произойти на {v_ref}, поэтому он не ' t добавить лишнее повторное заимствование на v_ref.

Теперь, когда мы знаем, как синтаксис метода решает вашу проблему неявно, у нас есть альтернативное решение для вашей проблемы в версии 2:

example2_inner(&mut *{ v_ref });
0 голосов
/ 27 августа 2018

Вы можете исправить это так:

v_ref = {
    // move `v_ref` to a new variable which will go out of scope by the end of the block
    let r = v_ref;
    // Mutating & slice splitting moved to function
    let (v_l, v_r) = example2_inner(r);

    match len % 2 {
        0 => v_l,
        _ => v_r,
    }
};

Причина в том, что v_1 и v_2 оба являются ссылками на v_ref, но эти ссылки оба переживают блок, потому что они возвращаются из него. Затем вы пытаетесь написать в v_ref, что не нравится средство проверки заимствований, потому что все еще существуют живые ссылки.

Присвоение v_ref новой переменной r означает, что v_ref больше не действителен - переменная была перемещена. Активные ссылки v_1 и v_2 теперь ссылаются на r, что, в свою очередь, является ссылкой на v и живет только столько, сколько блок. Теперь вы можете писать на v_ref, потому что больше ничего на него не ссылается.


В качестве альтернативы, вы можете просто обновить до Rust Edition 2018 или включить нелексические времена жизни, которые могут справиться с этой ситуацией.

...