第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を開催した。
プルリクエストはこちら。