Разделение заимствований работает только внутри одной функции. Однако здесь вы заимствуете поле 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));
}