Rust не допускает изменяемого заимствования при правильном разделении - PullRequest
2 голосов
/ 09 мая 2020
struct Test {
    a: i32,
    b: i32,
}

fn other(x: &mut i32, _refs: &Vec<&i32>) {
    *x += 1;
}

fn main() {
    let mut xes: Vec<Test> = vec![Test { a: 3, b: 5 }];
    let mut refs: Vec<&i32> = Vec::new();
    for y in &xes {
        refs.push(&y.a);
    }
    xes.iter_mut().for_each(|val| other(&mut val.b, &refs));
}

Хотя refs содержит только ссылки на a -элемент элементов в xes, а функция other использует b -часть, rust выдает следующую ошибку:

error[E0502]: cannot borrow `xes` as mutable because it is also borrowed as immutable
  --> /src/main.rs:16:5
   |
13 |     for y in &xes {
   |              ---- immutable borrow occurs here
...
16 |     xes.iter_mut().for_each(|val| other(&mut val.b, &refs));
   |     ^^^ mutable borrow occurs here                   ---- immutable borrow later captured here by closure

Детская площадка

Что-то не так с закрытием? Обычно это позволяет разделение заимствований . Что мне не хватает?

1 Ответ

3 голосов
/ 09 мая 2020

Разделение заимствований работает только внутри одной функции. Однако здесь вы заимствуете поле a в main и поле b в замыкании (которое, помимо возможности потреблять и заимствовать переменные из внешней области видимости, является отдельной функцией).

Начиная с Rust 1.43.1, сигнатуры функций не могут express детально заимствовать; когда в функцию передается ссылка (прямо или косвенно), она получает доступ к всем из нее. Проверка заимствования функций основана на сигнатурах функций; отчасти это связано с производительностью (вывод между функциями обходится дороже), отчасти для обеспечения совместимости по мере развития функции (особенно в библиотеке): то, что составляет действительный аргумент функции, не должно зависеть от реализации функции .

Насколько я понимаю, ваше требование состоит в том, чтобы вы имели возможность обновлять поле b ваших объектов на основе значения поля a всего набора объектов.

Я вижу два способа исправить это. Во-первых, мы можем захватить все изменяемые ссылки на b одновременно с захватом общих ссылок на a. Это правильный пример разделения займов. Обратной стороной этого подхода является то, что нам нужно выделить два Vec только для выполнения операции.

fn main() {
    let mut xes: Vec<Test> = vec![Test { a: 3, b: 5 }];
    let mut x_as: Vec<&i32> = Vec::new();
    let mut x_bs: Vec<&mut i32> = Vec::new();
    for x in &mut xes {
        x_as.push(&x.a);
        x_bs.push(&mut x.b);
    }
    x_bs.iter_mut().for_each(|b| other(b, &x_as));
}

Вот эквивалентный способ построения двух Vec с использованием итераторов:

fn main() {
    let mut xes: Vec<Test> = vec![Test { a: 3, b: 5 }];
    let (x_as, mut x_bs): (Vec<_>, Vec<_>) =
        xes.iter_mut().map(|x| (&x.a, &mut x.b)).unzip();
    x_bs.iter_mut().for_each(|b| other(b, &x_as));
}

Другой способ - полностью избежать изменяемых ссылок и использовать вместо них внутреннюю изменчивость. В стандартной библиотеке есть Cell, который хорошо работает для типов Copy, таких как i32, RefCell, который работает для всех типов, но заимствует проверку во время выполнения, добавляя небольшие накладные расходы, и Mutex и RwLock, который может использоваться в нескольких потоках, но выполнять проверки блокировок во время выполнения, поэтому не более одного потока получает доступ к внутреннему значению в любое время.

Вот пример с Cell. С помощью этого подхода мы можем исключить два временных Vec и передать всю коллекцию объектов в функцию other, а не просто ссылки на поле a.

use std::cell::Cell;

struct Test {
    a: i32,
    b: Cell<i32>,
}

fn other(x: &Cell<i32>, refs: &[Test]) {
    x.set(x.get() + 1);
}

fn main() {
    let xes: Vec<Test> = vec![Test { a: 3, b: Cell::new(5) }];
    xes.iter().for_each(|x| other(&x.b, &xes));
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...