Как переместить захваченную переменную в закрытие в закрытии? - программирование

Как переместить захваченную переменную в закрытие в закрытии?

Код - это неэффективный способ создания уникального набора элементов из итератора. Для этого я пытаюсь использовать Vec для отслеживания значений, которые я видел. Я считаю, что этот Vec должен принадлежать самому внутреннему закрытию:

fn main() {
    let mut seen = vec![];
    let items = vec![vec![1i32, 2], vec![3], vec![1]];

    let a: Vec<_> = items
        .iter()
        .flat_map(move |inner_numbers| {
            inner_numbers.iter().filter_map(move |&number| {
                if !seen.contains(&number) {
                    seen.push(number);
                    Some(number)
                } else {
                    None
                }
            })
        })
        .collect();

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

Однако компиляция не выполняется:

error[E0507]: cannot move out of captured outer variable in an `FnMut` closure
 --> src/main.rs:8:45
  |
2 |     let mut seen = vec![];
  |         -------- captured outer variable
...
8 |             inner_numbers.iter().filter_map(move |&number| {
  |                                             ^^^^^^^^^^^^^^ cannot move out of captured outer variable in an `FnMut` closure
4b9b3361

Ответ 1

Это немного удивительно, но это не ошибка.

flat_map принимает FnMut, так как нужно несколько раз вызвать его закрытие. Код с move на внутреннем замыкании не работает, потому что это замыкание создается несколько раз, один раз для каждого inner_numbers. Если я пишу закрытие в явном виде (т.е. Структура, в которой хранятся захваты и реализация одной из характеристик замыкания), ваш код выглядит (немного), например

struct OuterClosure {
    seen: Vec<i32>
}
struct InnerClosure {
    seen: Vec<i32>
}
impl FnMut(&Vec<i32>) -> iter::FilterMap<..., InnerClosure> for OuterClosure {
    fn call_mut(&mut self, (inner_numbers,): &Vec<i32>) -> iter::FilterMap<..., InnerClosure> {
        let inner = InnerClosure {
            seen: self.seen // uh oh! a move out of a &mut pointer
        };
        inner_numbers.iter().filter_map(inner)
    }
}
impl FnMut(&i32) -> Option<i32> for InnerClosure { ... }

Из-за чего становится яснее противозаконность: попытка выйти из переменной &mut OuterClosure.


Теоретически достаточно захватить изменяемую ссылку, поскольку seen только изменяется (не перемещается) внутри замыкания. Однако для этого нужно слишком лениться...

error: lifetime of `seen` is too short to guarantee its contents can be safely reborrowed
 --> src/main.rs:9:45
  |
9 |             inner_numbers.iter().filter_map(|&number| {
  |                                             ^^^^^^^^^
  |
note: `seen` would have to be valid for the method call at 7:20...
 --> src/main.rs:7:21
  |
7 |       let a: Vec<_> = items.iter()
  |  _____________________^
8 | |         .flat_map(|inner_numbers| {
9 | |             inner_numbers.iter().filter_map(|&number| {
10| |                 if !seen.contains(&number) {
... |
17| |         })
18| |         .collect();
  | |__________________^
note: ...but `seen` is only valid for the lifetime  as defined on the body at 8:34
 --> src/main.rs:8:35
  |
8 |           .flat_map(|inner_numbers| {
  |  ___________________________________^
9 | |             inner_numbers.iter().filter_map(|&number| {
10| |                 if !seen.contains(&number) {
11| |                     seen.push(number);
... |
16| |             })
17| |         })
  | |_________^

Удаление move приводит к тому, что фиксации закрытия работают как

struct OuterClosure<'a> {
    seen: &'a mut Vec<i32>
}
struct InnerClosure<'a> {
    seen: &'a mut Vec<i32>
}
impl<'a> FnMut(&Vec<i32>) -> iter::FilterMap<..., InnerClosure<??>> for OuterClosure<'a> {
    fn call_mut<'b>(&'b mut self, inner_numbers: &Vec<i32>) -> iter::FilterMap<..., InnerClosure<??>> {
        let inner = InnerClosure {
            seen: &mut *self.seen // can't move out, so must be a reborrow
        };
        inner_numbers.iter().filter_map(inner)
    }
}
impl<'a> FnMut(&i32) -> Option<i32> for InnerClosure<'a> { ... }

(Я назвал время жизни &mut self в этом, для педагогических целей.)

Этот случай определенно более тонкий. Итератор FilterMap хранит внутреннее закрытие, то есть любые ссылки в значении замыкания (то есть любые ссылки, которые он фиксирует) должны быть действительными до тех пор, пока значения FilterMap будут выбрасываться, а для &mut ссылок, любые ссылки должны быть осторожны, чтобы не сглаживаться.

Компилятор не может быть уверен, что flat_map не будет, например. сохраните все возвращенные итераторы в Vec<FilterMap<...>>, что приведет к тому, что куча псевдонимов &mut s... очень плохая! Я думаю, что это конкретное использование flat_map оказывается безопасным, но я не уверен, что это вообще, и, безусловно, функции с тем же типом подписи, что и flat_map (например, map), определенно будут unsafe. (Фактически, замена flat_map на map в коде дает ситуацию Vec, которую я только что описал.)

Для сообщения об ошибке: self эффективно (игнорирование обертки структуры) &'b mut (&'a mut Vec<i32>) где 'b - это время жизни &mut self reference, а 'a - время жизни ссылки в struct. Перемещение внутреннего &mut вне незаконно: не может перенести аффинный тип типа &mut из ссылки (это будет работать с &Vec<i32>, хотя), поэтому единственный выбор - перебор. Перебор происходит через внешнюю ссылку и поэтому не может ее пережить, т.е. &mut *self.seen reborrow - это &'b mut Vec<i32>, а не &'a mut Vec<i32>.

Это делает внутреннее замыкание типа InnerClosure<'b>, и, следовательно, метод call_mut пытается вернуть a FilterMap<..., InnerClosure<'b>>. К сожалению, показатель FnMut определяет call_mut как просто

pub trait FnMut<Args>: FnOnce<Args> {
    extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}

В частности, нет связи между временем жизни самой ссылки self и возвращаемым значением, и поэтому незаконно пытаться вернуть InnerClosure<'b>, у которого есть эта ссылка. Вот почему компилятор жалуется, что срок службы слишком короткий, чтобы иметь возможность переубеждать.

Это очень похоже на метод Iterator::next, и здесь код не работает, по той же причине, что итератор не может ссылаться на ссылки в памяти, которыми владеет сам итератор. (Я предполагаю, что "потоковой итератор" (итераторы со связью между &mut self и возвращаемым значением в next) библиотеке для обеспечения flat_map, который работает с почти написанным кодом: для этого нужны черты "закрытия" с аналогичной ссылкой.)

Работы включают:

  • RefCell, предложенный Ренато Занноном, который позволяет seen заимствовать как общий &. Код закрытого закрытия в основном такой же, как изменение &mut Vec<i32> до &Vec<i32>. Это изменение означает, что "reborrow" из &'b mut &'a RefCell<Vec<i32>> может быть просто копией &'a ... из &mut. Это буквальная копия, поэтому продолжительность жизни сохраняется.
  • избегая лени итераторов, чтобы избежать возврата внутреннего закрытия, в частности, .collect::<Vec<_>>() внутри цикла, чтобы пройти через весь filter_map перед возвратом.
  
fn main() {
    let mut seen = vec![];
    let items = vec![vec![1i32, 2], vec![3], vec![1]];

    let a: Vec<_> = items
        .iter()
        .flat_map(|inner_numbers| {
            inner_numbers
                .iter()
                .filter_map(|&number| if !seen.contains(&number) {
                    seen.push(number);
                    Some(number)
                } else {
                    None
                })
                .collect::<Vec<_>>()
                .into_iter()
        })
        .collect();

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

Я полагаю, что версия RefCell более эффективна.

Ответ 2

Похоже, что контролер заимствований путается с вложенными замыканиями + изменчивым заимствованием. Возможно, стоит написать вопрос. Изменить: см. huon answer, почему это не ошибка.

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

use std::cell::RefCell;

fn main() {
    let seen = vec![];
    let items = vec![vec![1i32, 2], vec![3], vec![1]];

    let seen_cell = RefCell::new(seen);

    let a: Vec<_> = items
        .iter()
        .flat_map(|inner_numbers| {
            inner_numbers.iter().filter_map(|&number| {
                let mut borrowed = seen_cell.borrow_mut();

                if !borrowed.contains(&number) {
                    borrowed.push(number);
                    Some(number)
                } else {
                    None
                }
            })
        })
        .collect();

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