[英]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
引用的生命周期, 'a
是struct
引用的生命周期. 將內部&mut
移出是非法的:不能將像&mut
這樣的仿射類型移出引用(不過它可以與&Vec<i32>
),因此唯一的選擇是重新借用。 重新借用正在通過外部引用,因此不能超過它,也就是說, &mut *self.seen
重新借用是&'b mut Vec<i32>
,而不是&'a mut Vec<i32>
。
這使得內部閉包具有InnerClosure<'b>
類型,因此call_mut
方法試圖返回一個FilterMap<..., InnerClosure<'b>>
。 不幸的是, FnMut
trait將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
建議雷納托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_map
和filter_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.