このコードは、イテレータから一意のアイテムセットを生成する非効率的な方法です。これを達成するために、私は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
ごとに1回ずつ作成されるため、失敗します。明示的な形式でクロージャ(つまり、キャプチャとクロージャトレイトの1つの実装を格納する構造体)を記述した場合、コードは(少し)のようになります。
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
としての署名(例:map
)は間違いなくunsafe
です。 (実際、コードでflat_map
をmap
に置き換えると、先ほど説明したVec
の状況になります。)
エラーメッセージの場合:self
は効果的です(構造体ラッパーを無視して)&'b mut (&'a mut Vec<i32>)
。ここで、'b
は&mut self
参照の存続期間であり、'a
はstruct
内の参照の存続期間です。内部の&mut
を移動することは違法です。&mut
のようなアフィン型を参照から移動することはできません(ただし、&Vec<i32>
で動作します)。 reborrowは外部参照を通過するため、それを超えることはできません。つまり、&mut *self.seen
reborrowは&'b mut Vec<i32>
ではなく&'a mut Vec<i32>
です。
これにより、内部クロージャーの型がInnerClosure<'b>
になるため、call_mut
メソッドはFilterMap<..., InnerClosure<'b>>
を返そうとします。残念ながら the 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
メソッドと非常に似ており、ここでのコードは、イテレータ自体が所有するメモリへの参照に対してイテレータを持つことができないのと基本的に同じ理由で失敗しています。 (私は "streaming iterator" (&mut self
とnext
の戻り値の間のリンクを持つイテレータ)ライブラリがほぼ書かれたコードで動作するflat_map
を提供できると想像します:同様のリンクを持つ「閉鎖」特性が必要になります。)
回避策は次のとおりです。
RefCell
。これにより、seen
を共有の&
として借用できます。脱糖されたクロージャーコードは、&mut Vec<i32>
を&Vec<i32>
に変更する以外は基本的に同じです。この変更は、&'b mut &'a RefCell<Vec<i32>>
の "reborrow"が&'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's 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);
}