クロージャ間で変数を共有できない場合にCellを使うのは適当か
Cell
は実行時に僅かなオーバーヘッドがあるのと、 借用ルールに違反した場合はコンパイルエラーではなく実行時エラー(panic)になるので、 もし使わないですむなら使わないほうがいいです。(2022年10月5日 訂正 :コメント欄でご指摘いただいたとおりCell
がpanicするというのは誤りでした。Cell
はpanicしないのが正しいです。RefCell
と混同してしまいました)
しかし、今回の質問のようなコードで変数を共有するにはCell
しかなさそうですので、Cell
を使うのは適当だと思います。
余談ですが、ご質問のコードで試したところ、片方のクロージャーを構造体で置き換え、その構造体にIterator
を実装することで、 Cell
を使わずに同じ機能を実現できました。簡略化前のコードに適用できるかはわかりませんが、参考になるかもしれません。
2022年10月5日 訂正 :コメント欄でご指摘いただいたとおり、うまく動く理由はMyIter::next
内でcollect
しているためでした。collectせず、Iterator::Item
をimpl Iterator
にすると、おっしゃる通り、inspectのクロージャーが &mut next_start
をキャプチャ(捕捉)しようとするので、元のコードと同じ所有権がらみのエラーになってしまいました。
rust
1 #[derive(Debug, Clone, PartialEq, Eq)]
2 enum Line2 {
3 A ,
4 B ,
5 }
6
7 // クロージャーとiter::from_fn()で作っていたイテレーターを構造体で置き換える
8 struct MyIter < I1 , F > {
9 lines : I1 ,
10 process_line : F ,
11 next_start : i32 ,
12 }
13
14 impl < I1 , F , I2 > Iterator for MyIter < I1 , F >
15 where
16 I1 : Iterator < Item = i32 > , // linesイテレーター
17 F : FnMut ( i32 ) -> I2 , // process_lineクロージャー
18 I2 : Iterator < Item = ( i32 , Line2 ) > , // process_lineの戻り値(イテレーター)
19 {
20 type Item = Vec < ( i32 , Line2 ) > ;
21
22 fn next ( & mut self ) -> Option < Self :: Item > {
23 let input_line = loop {
24 let input_line = self . lines . next ( ) ? ;
25 if input_line >= self . next_start {
26 break input_line ;
27 }
28 println! ( "{}: skipped" , input_line ) ;
29 } ;
30 println! ( "{}: processed" , input_line ) ;
31
32 Some ( ( self . process_line ) ( input_line )
33 . inspect ( | ( end , _ ) | { self . next_start = * end ; } )
34 . collect ( )
35 )
36 }
37 }
38
39 fn main ( ) {
40 // process_line の出力用
41 let mut process_line_results = [
42 ( 0 , & [ ( 1 , Line2 :: A ) , ( 1 , Line2 :: B ) ] [ .. ] ) ,
43 ( 1 , & [ ] ) ,
44 ( 2 , & [ ( 4 , Line2 :: B ) ] ) ,
45 // 3 はなし、4 まで飛ばすので飛ばされる
46 ( 4 , & [ ] ) ,
47 ( 5 , & [ ( 6 , Line2 :: A ) ] ) ,
48 ]
49 . into_iter ( ) ;
50
51 // 行の内容を処理して結果を返す処理
52 // ここでは process_line_results から順番に返すようにしている
53 // ここではクロージャだが、実際は関数
54 // 実際も戻り値は impl Iterator
55 let process_line = | i | {
56 // 計5回呼ばれる
57 let ( expected_i , line_items ) = process_line_results . next ( ) . unwrap ( ) ;
58 assert_eq! ( i , expected_i ) ;
59 line_items . into_iter ( ) . cloned ( ) // impl Iterator<Item=(i32, Line2)>
60 } ;
61
62 // 入力データ
63 // ここでは単なる数値 [0,1,2,3,4,5]
64 // 実際も impl Iterator
65 let lines = 0 .. 6_i32 ;
66
67 // ★メイン処理
68 let main_result = MyIter {
69 lines ,
70 process_line ,
71 next_start : 0 ,
72 }
73 . flatten ( )
74 . collect :: < Vec < _ >> ( ) ;
75
76 // 処理結果出力
77 println! ( "main_result: {:?}" , main_result ) ;
78 assert_eq! (
79 main_result ,
80 vec! [ ( 1 , Line2 :: A ) , ( 1 , Line2 :: B ) , ( 4 , Line2 :: B ) , ( 6 , Line2 :: A ) , ]
81 ) ;
82 }
2022年10月5日 追記
所有権の問題を回避するために、MyIter::next
がイテレーター(impl Iterator<Item=(i32, Line2)>
)を返すのではなく、(i32, Line2)
を返す実装にしてみました。
ただ、ここまでするなら、MyIter
をイテレーターにしてmain
でcollect
するよりも、イテレーターにせずにVec<(i32, Line2)>
を返すメソッドを作る方が簡単かもしれません。
rust
1 #[derive(Debug, Clone, PartialEq, Eq)]
2 enum Line2 {
3 A ,
4 B ,
5 }
6
7 // クロージャーとiter::from_fn()で作っていたイテレーターを構造体で置き換える
8 struct MyIter < I1 , F , I2 > {
9 input_lines_iter : I1 ,
10 process_line : F ,
11 processed_lines_iter : Option < I2 > ,
12 next_start : i32 ,
13 }
14
15 impl < I1 , F , I2 > MyIter < I1 , F , I2 > {
16 fn new ( input_lines_iter : I1 , process_line : F ) -> Self {
17 MyIter {
18 input_lines_iter ,
19 process_line ,
20 processed_lines_iter : None ,
21 next_start : 0 ,
22 }
23 }
24 }
25
26 impl < I1 , F , I2 > Iterator for MyIter < I1 , F , I2 >
27 where
28 I1 : Iterator < Item = i32 > , // input_linesイテレーター
29 F : FnMut ( i32 ) -> I2 , // process_lineクロージャー
30 I2 : Iterator < Item = ( i32 , Line2 ) > , // process_lineの戻り値
31 {
32 type Item = ( i32 , Line2 ) ; // このイテレーターの戻り値型
33
34 fn next ( & mut self ) -> Option < Self :: Item > {
35 loop {
36 // process_lineの戻り値のイテレーターがないなら、process_lineを呼び出して
37 // イテレーターを作る
38 if self . processed_lines_iter . is_none ( ) {
39 let input_line = loop {
40 let input_line = self . input_lines_iter . next ( ) ? ;
41 if input_line >= self . next_start {
42 break input_line ;
43 }
44 println! ( "{}: skipped" , input_line ) ;
45 } ;
46 println! ( "{}: processed" , input_line ) ;
47
48 self . processed_lines_iter = Some ( ( self . process_line ) ( input_line ) ) ;
49 }
50
51 if let Some ( pl_iter ) = & mut self . processed_lines_iter {
52 // process_lineの戻り値のイテレーターから次の値を取り出す
53 if let Some ( pl @ ( end , _ ) ) = pl_iter . next ( ) {
54 self . next_start = end ;
55 return Some ( pl ) ;
56 } else {
57 // イテレーターが終了していたら、最初からやり直す
58 self . processed_lines_iter = None ;
59 continue ;
60 }
61 } else {
62 // 全ての行を処理したので終了する
63 return None ;
64 }
65 }
66 }
67 }
68
69 fn main ( ) {
70 // process_line の出力用
71 let mut process_line_results = [
72 ( 0 , & [ ( 1 , Line2 :: A ) , ( 1 , Line2 :: B ) ] [ .. ] ) ,
73 ( 1 , & [ ] ) ,
74 ( 2 , & [ ( 4 , Line2 :: B ) ] ) ,
75 // 3 はなし、4 まで飛ばすので飛ばされる
76 ( 4 , & [ ] ) ,
77 ( 5 , & [ ( 6 , Line2 :: A ) ] ) ,
78 ]
79 . into_iter ( ) ;
80
81 // 行の内容を処理して結果を返す処理
82 // ここでは process_line_results から順番に返すようにしている
83 // ここではクロージャだが、実際は関数
84 // 実際も戻り値は impl Iterator
85 let process_line = | i | {
86 // 計5回呼ばれる
87 let ( expected_i , line_items ) = process_line_results . next ( ) . unwrap ( ) ;
88 assert_eq! ( i , expected_i ) ;
89 line_items . into_iter ( ) . cloned ( ) // impl Iterator<Item=(i32, Line2)>
90 } ;
91
92 // 入力データ
93 // ここでは単なる数値 [0,1,2,3,4,5]
94 // 実際も impl Iterator
95 let lines = 0 .. 6_i32 ;
96
97 // ★メイン処理
98 // flattenが不要になった
99 let main_result = MyIter :: new ( lines , process_line ) . collect :: < Vec < _ >> ( ) ;
100
101 // 処理結果出力
102 println! ( "main_result: {:?}" , main_result ) ;
103 assert_eq! (
104 main_result ,
105 vec! [ ( 1 , Line2 :: A ) , ( 1 , Line2 :: B ) , ( 4 , Line2 :: B ) , ( 6 , Line2 :: A ) , ]
106 ) ;
107 }
下記のような回答は推奨されていません。
このような回答には修正を依頼しましょう。
また依頼した内容が修正された場合は、修正依頼を取り消すようにしましょう。
2022/10/03 12:49
2022/10/05 09:06
2022/10/06 15:27