Смежный вопрос: Не могу заимствовать изменчиво в двух разных замыканиях в одной и той же области действия
Я считаю, что моя ситуация достаточно отличается, потому что замыкания в моем случае вызываются только один раз, в целях. Я надеюсь, что есть способ убедить компилятора в том, что я делаю, безопасно, что, возможно, было невозможно в другом вопросе.
Я прошу прощения, что этот вопрос довольно многословен. Я пытался свести к минимуму код и удалить ненужные детали, но я не хочу ограничиваться тем дизайном, который у меня есть в настоящее время - я подозреваю, что есть лучший способ приблизиться к этому в целом.
У меня есть вложенная структура и функция, которая находит в ней элемент. Вызывающий эту функцию должен видоизменить как элемент, так и его родителя. Это создает проблему, так как я не могу вернуть & mut родителю, пока & mut ребенку позаимствован у него. Я также хочу избежать повторного обхода структуры, чтобы найти родителя после мутации ребенка. То, что я хочу, это способ «вернуть» ссылку на потомка, а затем мутировать родителя.
На данный момент мое решение заключается в том, что функция, которая находит элемент - давайте назовем его with_something
- принимает два обратных вызова FnOnce: один для изменения дочернего элемента, другой для изменения родительского. Он вызывает их в таком порядке, как только находит предмет на более низком уровне. Пока все хорошо.
Проблема в том, что в этих двух обратных вызовах мне также нужно изменить локальную переменную в вызывающей функции (в частности, HashSet, чтобы отслеживать, какие элементы были изменены). Теперь компилятор мне больше не доверяет. Он считает использование двух обратных вызовов HashSet одновременными изменяемыми ссылками. Я полагаю, это потому, что они могут быть вызваны в отдельных потоках; Я не могу думать о том, как иначе это было бы проблемой (но я хотел бы быть просветленным в этом вопросе).
Вот попытка уменьшить мой код:
use std::collections::HashSet;
fn main() {
let mut touched_items: HashSet<i32> = HashSet::new();
with_something(
|inner_inner| {
touched_items.insert(inner_inner.value);
inner_inner.value = 10;
},
|inner| {
touched_items.insert(inner.value);
inner.value = 20;
},
);
println!("touched_items: {:?}", touched_items);
}
fn with_something<F, G>(f: F, g: G)
where
F: FnOnce(&mut InnerInnerState),
G: FnOnce(&mut InnerState),
{
let mut state = SomethingWithState {
value: 5,
inner: InnerState {
value: 17,
inner: InnerInnerState { value: 40 },
},
};
f(&mut state.inner.inner);
g(&mut state.inner);
state.value = 50;
}
struct SomethingWithState {
value: i32,
inner: InnerState,
}
struct InnerState {
value: i32,
inner: InnerInnerState,
}
struct InnerInnerState {
value: i32,
}
И ошибка:
error[E0499]: cannot borrow `touched_items` as mutable more than once at a time
--> src/main.rs:10:9
|
5 | with_something(
| -------------- first borrow later used by call
6 | |inner_inner| {
| ------------- first mutable borrow occurs here
7 | touched_items.insert(inner_inner.value);
| ------------- first borrow occurs due to use of `touched_items` in closure
...
10 | |inner| {
| ^^^^^^^ second mutable borrow occurs here
11 | touched_items.insert(inner.value);
| ------------- second borrow occurs due to use of `touched_items` in closure
Есть ли способ, которым я могу объявить with_something
, чтобы компилятор знал, что два замыкания выполняются последовательно и до возврата из функции, и, следовательно, безопасно принимать изменяемый ref к тому же в каждом? (Или я что-то неправильно понимаю, и на самом деле был бы способ сделать что-то небезопасное?)
Я понимаю, что ответ может быть «нет», поэтому я также был бы признателен за указатели на лучший обходной путь, чем тот, который у меня есть, или лучший способ решения этой проблемы с самого начала.
Мой обходной путь - который я нашел в процессе написания этого вопроса, так что спасибо за это - проходит состояние окружающей среды, которое мне нужно преобразовать в with_something
и обратно в замыкания как & mut. Это то, что я сейчас делаю, так как он поддерживает общий поток кода в вызывающей функции. Я сделал его обобщенным c относительно типа состояния, чтобы избежать слишком тесного связывания with_something
со своими пользователями, но он все еще ощущается как излишне грязный API.
fn main() {
// ...
with_something(
&mut touched_items,
|inner_inner, touched_items| {
touched_items.insert(inner_inner.value);
inner_inner.value = 10;
},
|inner, touched_items| {
touched_items.insert(inner.value);
inner.value = 20;
},
);
// ...
}
fn with_something<F, G, S>(pass_through_state: &mut S, f: F, g: G)
where
F: FnOnce(&mut InnerInnerState, &mut S),
G: FnOnce(&mut InnerState, &mut S),
{
// ...
f(&mut state.inner.inner, pass_through_state);
g(&mut state.inner, pass_through_state);
// ...
}
(опущен некоторый код, который был таким же, как и оригинал, но эта версия компилируется и работает, как и ожидалось.)
Я также рассмотрел создание свойства, определяющего два обратных вызова с помощью & mut self, и реализацию его со структурой, которая содержит HashSet. Но это по сути то же самое, что и прохождение через & mu. Я полагаю, что на первый взгляд функция подписи функции будет выглядеть чище, на самом деле это не будет лучше API, а на сайте вызовов будет более сложный и нестандартный код, что, похоже, не стоит.
В любом случае, определение with_something
зависит от деталей вызывающего абонента, которые не связаны с его функцией. Я думаю, это то, что я пытаюсь избежать.