LINUX.ORG.RU

Borrow checker в rust лютует

 ,


1

5

Почему borrow checker не принимает код (cannot borrow `v` as immutable because it is also borrowed as mutable)

let mut v = vec![0];
v[v.len() - 1] = 1;

в то время как код, в который по идее должен бы скомпилиться приведенный выше

use std::ops::IndexMut;
*v.index_mut(v.len() - 1) = 1;

работает как положено

★★★

Ответ на: комментарий от quantum-troll

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

q0tw4 ★★★ ()

Код требует небольшого рефакторинга, потому что у тебя одновременно используется mutable reference и immutable, что запрещено правилами. К этому правилу надо просто привыкнуть. Оно тебя еще не раз спасет при написании кода.

Легко переписать как:

    let mut v = vec![0];
    let n = v.len();
    v[n - 1] = 1;

В твоем же случае компилятор предельно ясно изложил проблему:

$ cat main.rs

fn main() {
    let mut v = vec![0];
    v[v.len() - 1] = 1;
}

$ rustc main.rs
error[E0502]: cannot borrow `v` as immutable because it is also borrowed as mutable
 --> main.rs:4:7
  |
4 |     v[v.len() - 1] = 1;
  |     - ^          - mutable borrow ends here
  |     | |
  |     | immutable borrow occurs here
  |     mutable borrow occurs here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0502`.
dave ★★★★★ ()
Ответ на: комментарий от dave

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

q0tw4 ★★★ ()

По моему это второй вариант не должен компилироваться.

Прочитал обсуждение бага. Поправка. Оба варианта не должны компилироваться до NLL (нелексические лайфтаймы), NLL должен был сделать оба варианта валидными. Это недоработка NLL.

vlad9486 ()
Последнее исправление: vlad9486 (всего исправлений: 1)
Ответ на: комментарий от anonymous

Безопасность - это следствие правил. Правила ограничивают сильнее чем нужно для одной лишь безопасности. И просто так нарушать их ради частных случаев - плохо. Но NNL это не нарушение правил, а их модификация, так что ждём.

vlad9486 ()
Ответ на: комментарий от vlad9486

Так и не понял причем тут новые правила лайфтаймов. По завершении функции len ссылка на вектор, передаваемая в него в любом случае должна выходить из области видимости изнутри len. И только потом запускается index_mut. Это видно после перегонки кода в императивную форму, в которой все фактические аргументы либо литералы, либо переменные. Разве было как-то иначе? Тогда непонятно как оно вообще раньше разрешало хоть что-то на себе писать.

q0tw4 ★★★ ()
Ответ на: комментарий от vlad9486

А да, вопрос в догонку: в коде

let mut v = vec![0];
for x in v.iter_mut() {
  *x += v.len()
}

вроде как понятно, почему оно ругается, но реально то оно вполне валидно. Так вот, можно ли как-то выразить, что у нас есть модифицирующий доступ к элементам вектора, который гарантированно не убъет сам вектор и даже не изменит его длину? Через какой нибудь VecView например. Или тут в принципе ничего не поделаешь, поскольку как и в С++ перепутаны понятия константности участка памяти и константности объекта вместе с индирекцией памяти. В смысле константность переменной и константность значения попутаны, хотя это немного разные вещи. В случае вектора переменная типа вектор - это указатель на данные и длина, а значение - это элементы вектора.

q0tw4 ★★★ ()
Ответ на: комментарий от q0tw4

Насколько я сам понял, заимствования заканчивается во время биндинга. Заимствует выражение. Оно одно заимствует «одновременно» все что в него входит. *v.index_mut(v.len() - 1) = 1; тогда тоже не должно компилироваться, но это уже NLL сделал его валидным. Что-бы проверить мое предположение нужно попробовать компилировать это старым компилятором без NLL.

vlad9486 ()
Ответ на: комментарий от q0tw4

v.iter_mut() живет весь цикл. Пока это значение живо, v залочен. Думаю что да, можно сделать модифицирующий доступ, который не захватывает вектор. С помощью unsafe, но кмк лучше просто сделать биндинг let v_len = v.len().

vlad9486 ()
Ответ на: комментарий от vlad9486

А не лучше было бы сделать любую итерацию и мутабельную ссылку на элемент по немутабельной ссылке вектора, в то время как мутабельными для вектора оставить только изменяющие длину методы? Ну и вообще шикос был бы если бы можно было объявлять Vec<mut T> с вышеперечисленными свойствами, который легко кастовался бы к привычному нам Vec<T>.

q0tw4 ★★★ ()
Ответ на: комментарий от q0tw4

А не лучше было бы сделать любую итерацию и мутабельную ссылку на элемент по немутабельной ссылке вектора, в то время как мутабельными для вектора оставить только изменяющие длину методы?

тогда можно будет две мутабельные ссылки на один элемент получить - поломается базовый инвариант

MyTrooName ★★★★★ ()
Ответ на: комментарий от q0tw4

Ну так можно придумать некий враппер, который будет мутабельно завязан с элементом

с одним элементом? яннп, но скорее всего либо не выйдет, либо будет бесполезно.

Cell и RefCell проверяют те же правила в рантайме, но с ними можно работать

MyTrooName ★★★★★ ()
Ответ на: комментарий от MyTrooName

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

q0tw4 ★★★ ()
Последнее исправление: q0tw4 (всего исправлений: 1)
Ответ на: комментарий от q0tw4

ну да, звучит как RefCell<Vec<_>>. делаешь borrow_mut по необходимости, на короткие промежутки времени. но это оверхед, и вообще переусложнение. лучше вынести let n = v.len() в начало, если есть возможность

Vec<Cell<_>>, наверное, даст вектор с типа «мутабельными» элементами и немутабельной длиной, без оверхеда.

иначе хватит вектора умных указателей

это лишняя аллокация, как минимум

MyTrooName ★★★★★ ()
Последнее исправление: MyTrooName (всего исправлений: 1)
Ответ на: комментарий от anonymous

Cell никакого оверхеда не имеет

в смысле проверок в рантайме нет, точно. оверхед будет на перезаписи ячейки вместо взятия ссылки, если там что-то крупнее usize

MyTrooName ★★★★★ ()