第51回です。前回はこちら。
[第51回の様子]
2022/08/17に第51回を開催した。
内容としてはRust By Example 日本語版の16. トレイトの続きで、「16.6. impl Trait」、「16.7. クローン」に取り組んだ。
参加者は自分を入れて6人。今週はいつもより少し多かった!
[学んだこと]
- 16.6. impl Trait
- impl トレイトの書き方は、関数の引数または戻り値で利用できる
- 元々、ジェネリクスを利用していたところをimplを利用して書き直すと次のようになる
// ジェネリクスバージョン fn parse_csv_document<R: std::io::BufRead>(src: R) -> std::io::Result<Vec<Vec<String>>> { // 省略 } // implバージョン fn parse_csv_document(src: impl std::io::BufRead) -> std::io::Result<Vec<Vec<String>>> { // 省略 }
fn main() { // 型を明示する parse_csv_document::<std::io::Empty>(std::io::empty()); // 型を明示しない parse_csv_document(std::io::empty()); } // implバージョンの場合、以下のコンパイルエラー error[E0107]: this function takes 0 generic arguments but 1 generic argument was supplied --> src/main.rs:16:5 | 16 | parse_csv_document::<std::io::Empty>(std::io::empty()); | ^^^^^^^^^^^^^^^^^^------------------ help: remove these generics | | | expected 0 generic arguments | note: function defined here, with 0 generic parameters --> src/main.rs:1:4 | 1 | fn parse_csv_document(src: impl std::io::BufRead) -> std::io::Result<Vec<Vec<String>>> { | ^^^^^^^^^^^^^^^^^^ = note: `impl Trait` cannot be explicitly specified as a generic argument
- 戻り値でも同じようにimplを利用できる
use std::iter; use std::vec::IntoIter; // 2つのVec<i32>を結合したイテレータを返す fn combine_vecs_explicit_return_type( v: Vec<i32>, u: Vec<i32>, ) -> iter::Cycle<iter::Chain<IntoIter<i32>, IntoIter<i32>>> { v.into_iter().chain(u.into_iter()).cycle() } // 戻り値の型がすごくシンプルに! fn combine_vecs( v: Vec<i32>, u: Vec<i32>, ) -> impl Iterator<Item=i32> { v.into_iter().chain(u.into_iter()).cycle() }
- 重要なポイントとして、クロージャは無名の型を持つので、戻り値として型を明示することができない
- これをimpl Fnなどを利用して解決できる
- ほとんど覚えていなかったが、以前9.2.5. クロージャを返す関数でやった内容である
fn make_adder_function(y: i32) -> impl Fn(i32) -> i32 { let closure = move |x: i32| { x + y }; closure } fn main() { let plus_one = make_adder_function(1); assert_eq!(plus_one(2), 3); }
- これをimplを使わずに書くとBoxを使って以下のように書く
// A traditional edition of make_adder_function. fn make_adder_function_traditional(y: i32) -> Box<dyn Fn(i32) -> i32> { let closure = move |x: i32| { x + y }; Box::new(closure) }
- mapやfilterにクロージャを渡す場合もシンプルに書ける
fn double_positives<'a>(numbers: &'a Vec<i32>) -> impl Iterator<Item = i32> + 'a { numbers .iter() .filter(|x| x > &&0) .map(|x| x * 2) } fn main() { let singles = vec![-3, -2, 2, 3]; let doubles = double_positives(&singles); assert_eq!(doubles.collect::<Vec<i32>>(), vec![4, 6]); }
fn larger_than_zero(n: &&i32) -> bool { n > &&0 } fn times_two(n: &i32) -> i32 { n * &2 } fn double_positives_traditional<'a>( numbers: &'a Vec<i32> ) -> iter::Map<iter::Filter<std::slice::Iter<'a, i32>, fn(&&i32) -> bool>, fn(&i32) -> i32> { numbers .iter() .filter(larger_than_zero as fn(&&i32) -> bool) // convert larger_than_zero to a function pointer .map(times_two) }
- 16.7. クローン
- ここはまあこれまでにやってきたことの復習みたいなパート。
-
メモリ上の資源は、デフォルトでは変数束縛や関数呼び出しの際にムーブが発生する
-
ムーブの後は元の変数にアクセスできなくなる
-
-
そうではなく、資源をコピーしたい時は、Cloneトレイトを実装し、.clone()メソッドでコピーできる
-
ムーブの代わりにコピーを行いたい場合は、追加でCopyトレイトを実装する必要がある
-
// 資源なし構造体 #[derive(Debug, Clone, Copy)] struct Unit; // `Clone`トレイトを実装する型の変数を資源として持つタプル #[derive(Clone, Debug)] struct Pair(Box<i32>, Box<i32>); fn main() { let unit = Unit; // Unitをコピー let copied_unit = unit; // コピー元にもアクセスできる println!("original: {:?}", unit); println!("copy: {:?}", copied_unit); let pair = Pair(Box::new(1), Box::new(2)); println!("original: {:?}", pair); // moved_pairにムーブする。 let moved_pair = pair; println!("moved: {:?}", moved_pair); // 元の変数にはアクセスできない //println!("original: {:?}", pair); // TODO ^ Try uncommenting this line // クローンする場合 let cloned_pair = moved_pair.clone(); // 元の変数をドロップ drop(moved_pair); // 元の変数はドロップ済み //println!("copy: {:?}", moved_pair); // .clone()した値はまだ使用可能! println!("clone: {:?}", cloned_pair); }
[まとめ]
モブプログラミングスタイルでRust dojoを開催した。
プルリクエストはこちら。