簡體   English   中英

如何將捕獲的變量移動到閉包內的閉包中?

[英]How can I move a captured variable into a closure within a closure?

此代碼是一種從迭代器生成唯一項集的低效方式。 為了實現這一點,我試圖使用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

這有點令人驚訝,但不是錯誤。

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 s 使閉包捕獲像

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具有相同簽名風格的flat_map (例如map )肯定是unsafe (實際上,代碼中用map替換flat_map給出了我剛剛描述的Vec情況。)

對於錯誤消息: self是有效的(忽略結構包裝器) &'b mut (&'a mut Vec<i32>)其中'b&mut self引用的生命周期, 'astruct引用的生命周期. 將內部&mut移出是非法的:不能將像&mut這樣的仿射類型移出引用(不過它可以與&Vec<i32> ),因此唯一的選擇是重新借用。 重新借用正在通過外部引用,因此不能超過它,也就是說, &mut *self.seen重新借用是&'b mut Vec<i32> ,而不是&'a mut Vec<i32>

這使得內部閉包具有InnerClosure<'b>類型,因此call_mut方法試圖返回一個FilterMap<..., InnerClosure<'b>> 不幸的是, FnMut traitcall_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 selfnext的返回值之間有鏈接的迭代器)庫將能夠提供一個flat_map ,它可以與幾乎編寫的代碼一起使用:需要具有類似鏈接的“閉包”特征。 )

變通辦法包括:

  • 所述RefCell建議雷納托Zannon,其允許seen要借用作為共享& 脫糖閉合代碼基本上不是改變相同的其他&mut Vec<i32>&Vec<i32> 此更改意味着&'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版本更有效。

似乎借用檢查器對嵌套閉包 + 可變借用感到困惑。 可能值得提出問題。 編輯:有關為什么這不是錯誤的原因,請參閱huon 的回答

作為一種解決方法,可以在此處使用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);
}

在遇到類似問題后,我遇到了這個問題,使用flat_mapfilter_map 我通過將filter_map flat_map閉包之外解決了這個問題。

使用您的示例:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM