回答編集履歴
2
コメントでのご指摘を受けて回答を訂正しました。また、別の解決法を追記しました。
test
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
> クロージャ間で変数を共有できない場合にCellを使うのは適当か
|
2
2
|
|
3
|
-
`Cell`は実行時に僅かなオーバーヘッドがあるのと、借用ルールに違反した場合はコンパイルエラーではなく実行時エラー(panic)になるので、もし使わないですむなら使わないほうがいいです。
|
3
|
+
`Cell`は実行時に僅かなオーバーヘッドがあるのと、 ~~借用ルールに違反した場合はコンパイルエラーではなく実行時エラー(panic)になるので、~~ もし使わないですむなら使わないほうがいいです。(**2022年10月5日 訂正**:コメント欄でご指摘いただいたとおり`Cell`がpanicするというのは誤りでした。`Cell`はpanicしないのが正しいです。`RefCell`と混同してしまいました)
|
4
4
|
|
5
5
|
しかし、今回の質問のようなコードで変数を共有するには`Cell`しかなさそうですので、`Cell`を使うのは適当だと思います。
|
6
6
|
|
7
|
-
余談ですが、ご質問のコードで試したところ、片方のクロージャーを構造体で置き換え、その構造体に`Iterator`を実装することで、`Cell`を使わずに同じ機能を実現できました。簡略化前のコードに適用できるかはわかりませんが、参考になるかもしれません。
|
7
|
+
余談ですが、ご質問のコードで試したところ、片方のクロージャーを構造体で置き換え、その構造体に`Iterator`を実装することで、 ~~`Cell`を使わずに同じ機能を実現できました。簡略化前のコードに適用できるかはわかりませんが、参考になるかもしれません。~~
|
8
|
+
|
9
|
+
**2022年10月5日 訂正**:コメント欄でご指摘いただいたとおり、うまく動く理由は`MyIter::next`内で`collect`しているためでした。collectせず、`Iterator::Item`を`impl Iterator`にすると、おっしゃる通り、inspectのクロージャーが `&mut next_start` をキャプチャ(捕捉)しようとするので、元のコードと同じ所有権がらみのエラーになってしまいました。
|
8
10
|
|
9
11
|
```rust
|
10
12
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
@@ -91,3 +93,119 @@
|
|
91
93
|
}
|
92
94
|
```
|
93
95
|
|
96
|
+
**2022年10月5日 追記**
|
97
|
+
|
98
|
+
所有権の問題を回避するために、`MyIter::next`がイテレーター(`impl Iterator<Item=(i32, Line2)>`)を返すのではなく、`(i32, Line2)`を返す実装にしてみました。
|
99
|
+
|
100
|
+
ただ、ここまでするなら、`MyIter`をイテレーターにして`main`で`collect`するよりも、イテレーターにせずに`Vec<(i32, Line2)>`を返すメソッドを作る方が簡単かもしれません。
|
101
|
+
|
102
|
+
```rust
|
103
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
104
|
+
enum Line2 {
|
105
|
+
A,
|
106
|
+
B,
|
107
|
+
}
|
108
|
+
|
109
|
+
// クロージャーとiter::from_fn()で作っていたイテレーターを構造体で置き換える
|
110
|
+
struct MyIter<I1, F, I2> {
|
111
|
+
input_lines_iter: I1,
|
112
|
+
process_line: F,
|
113
|
+
processed_lines_iter: Option<I2>,
|
114
|
+
next_start: i32,
|
115
|
+
}
|
116
|
+
|
117
|
+
impl<I1, F, I2> MyIter<I1, F, I2> {
|
118
|
+
fn new(input_lines_iter: I1, process_line: F) -> Self {
|
119
|
+
MyIter {
|
120
|
+
input_lines_iter,
|
121
|
+
process_line,
|
122
|
+
processed_lines_iter: None,
|
123
|
+
next_start: 0,
|
124
|
+
}
|
125
|
+
}
|
126
|
+
}
|
127
|
+
|
128
|
+
impl<I1, F, I2> Iterator for MyIter<I1, F, I2>
|
129
|
+
where
|
130
|
+
I1: Iterator<Item = i32>, // input_linesイテレーター
|
131
|
+
F: FnMut(i32) -> I2, // process_lineクロージャー
|
132
|
+
I2: Iterator<Item = (i32, Line2)>, // process_lineの戻り値
|
133
|
+
{
|
134
|
+
type Item = (i32, Line2); // このイテレーターの戻り値型
|
135
|
+
|
136
|
+
fn next(&mut self) -> Option<Self::Item> {
|
137
|
+
loop {
|
138
|
+
// process_lineの戻り値のイテレーターがないなら、process_lineを呼び出して
|
139
|
+
// イテレーターを作る
|
140
|
+
if self.processed_lines_iter.is_none() {
|
141
|
+
let input_line = loop {
|
142
|
+
let input_line = self.input_lines_iter.next()?;
|
143
|
+
if input_line >= self.next_start {
|
144
|
+
break input_line;
|
145
|
+
}
|
146
|
+
println!("{}: skipped", input_line);
|
147
|
+
};
|
148
|
+
println!("{}: processed", input_line);
|
149
|
+
|
150
|
+
self.processed_lines_iter = Some((self.process_line)(input_line));
|
151
|
+
}
|
152
|
+
|
153
|
+
if let Some(pl_iter) = &mut self.processed_lines_iter {
|
154
|
+
// process_lineの戻り値のイテレーターから次の値を取り出す
|
155
|
+
if let Some(pl @ (end, _)) = pl_iter.next() {
|
156
|
+
self.next_start = end;
|
157
|
+
return Some(pl);
|
158
|
+
} else {
|
159
|
+
// イテレーターが終了していたら、最初からやり直す
|
160
|
+
self.processed_lines_iter = None;
|
161
|
+
continue;
|
162
|
+
}
|
163
|
+
} else {
|
164
|
+
// 全ての行を処理したので終了する
|
165
|
+
return None;
|
166
|
+
}
|
167
|
+
}
|
168
|
+
}
|
169
|
+
}
|
170
|
+
|
171
|
+
fn main() {
|
172
|
+
// process_line の出力用
|
173
|
+
let mut process_line_results = [
|
174
|
+
(0, &[(1, Line2::A), (1, Line2::B)][..]),
|
175
|
+
(1, &[]),
|
176
|
+
(2, &[(4, Line2::B)]),
|
177
|
+
// 3 はなし、4 まで飛ばすので飛ばされる
|
178
|
+
(4, &[]),
|
179
|
+
(5, &[(6, Line2::A)]),
|
180
|
+
]
|
181
|
+
.into_iter();
|
182
|
+
|
183
|
+
// 行の内容を処理して結果を返す処理
|
184
|
+
// ここでは process_line_results から順番に返すようにしている
|
185
|
+
// ここではクロージャだが、実際は関数
|
186
|
+
// 実際も戻り値は impl Iterator
|
187
|
+
let process_line = |i| {
|
188
|
+
// 計5回呼ばれる
|
189
|
+
let (expected_i, line_items) = process_line_results.next().unwrap();
|
190
|
+
assert_eq!(i, expected_i);
|
191
|
+
line_items.into_iter().cloned() // impl Iterator<Item=(i32, Line2)>
|
192
|
+
};
|
193
|
+
|
194
|
+
// 入力データ
|
195
|
+
// ここでは単なる数値 [0,1,2,3,4,5]
|
196
|
+
// 実際も impl Iterator
|
197
|
+
let lines = 0..6_i32;
|
198
|
+
|
199
|
+
// ★メイン処理
|
200
|
+
// flattenが不要になった
|
201
|
+
let main_result = MyIter::new(lines, process_line).collect::<Vec<_>>();
|
202
|
+
|
203
|
+
// 処理結果出力
|
204
|
+
println!("main_result: {:?}", main_result);
|
205
|
+
assert_eq!(
|
206
|
+
main_result,
|
207
|
+
vec![(1, Line2::A), (1, Line2::B), (4, Line2::B), (6, Line2::A),]
|
208
|
+
);
|
209
|
+
}
|
210
|
+
```
|
211
|
+
|
1
誤字を修正しました
test
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
しかし、今回の質問のようなコードで変数を共有するには`Cell`しかなさそうですので、`Cell`を使うのは適当だと思います。
|
6
6
|
|
7
|
-
余談ですが、ご質問コードで試したところ、片方のクロージャーを構造体で置き換え、その構造体に`Iterator`を実装することで、`Cell`を使わずに同じ機能を実現できました。簡略化前のコードに適用できるかはわかりませんが、参考になるかもしれません。
|
7
|
+
余談ですが、ご質問のコードで試したところ、片方のクロージャーを構造体で置き換え、その構造体に`Iterator`を実装することで、`Cell`を使わずに同じ機能を実現できました。簡略化前のコードに適用できるかはわかりませんが、参考になるかもしれません。
|
8
8
|
|
9
9
|
```rust
|
10
10
|
#[derive(Debug, Clone, PartialEq, Eq)]
|