Rust dojo第51回を開催した

第51回です。前回はこちら。

kdnakt.hatenablog.com

 

 

[第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()
}
  • 重要なポイントとして、クロージャは無名の型を持つので、戻り値として型を明示することができない
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)
}
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]);
}
  • implなしバージョンの実装は以下のようになる。戻り値の型がiter::Mapとかiter::Filterを組み合わせた型になっていてそもそも型を書くのが辛い
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を開催した。

 

プルリクエストはこちら。

github.com