Bitterless Rust

動的なやつら

語弊注意: この章の説明は特に語弊が激しい.ここで説明する内容は,正確には「所有権」「ムーブセマンティクス」「借用」と呼ばれる Rust の中核概念を極限まで簡略化したもの.正確な理解は The Book の第 4 章 を参照.

「動的なやつ」とは

Rust の型には 2 種類ある(語弊あり):

種類 特徴
サイズが決まってるやつ i32, f64, bool, char コピーできる.何も考えなくていい
サイズが動的なやつ String, Vec<T> なんか特殊なルールがある

i32f64 は常に 4 バイトとか 8 バイトとか,サイズが決まっている.こいつらは代入したり関数に渡したりしても自動でコピーされる:

fn main() {
    let a = 42;
    let b = a;     // コピーされる
    println!("{}", a); // OK! a はまだ使える
}

String と Vec は特殊

StringVec は中身の長さが変わる.こいつらには特殊なルールがある:

fn main() {
    let a = String::from("hello");
    let b = a;     // a の中身が b に「移動」する
    // println!("{}", a); // コンパイルエラー!a はもう使えない
    println!("{}", b); // OK
}

なんで? — 今は深く考えなくていい.「動的なやつは一度渡すと元から消える」とだけ覚えておこう.

関数に渡しても同じ:

fn print_it(s: String) {
    println!("{}", s);
}

fn main() {
    let name = String::from("Alice");
    print_it(name);
    // println!("{}", name); // エラー!name はもう使えない
}

解決策: clone

困ったら .clone() すればいい.コピーが作られるので元も使える:

fn main() {
    let a = String::from("hello");
    let b = a.clone();    // コピーを作る
    println!("{}", a);    // OK!
    println!("{}", b);    // OK!
}
fn print_it(s: String) {
    println!("{}", s);
}

fn main() {
    let name = String::from("Alice");
    print_it(name.clone());   // コピーを渡す
    println!("{}", name);     // OK!
}

.clone() ってメモリの無駄遣いでは?」— そう.でも今は気にしない.動けば正義.パフォーマンスが問題になったときに最適化すればいい.

Vec: 動的な配列

Vec は長さが変わる配列:

fn main() {
    // 作り方いろいろ
    let mut numbers: Vec<i32> = Vec::new();
    numbers.push(1);
    numbers.push(2);
    numbers.push(3);

    // マクロで一発
    let colors = vec!["red", "green", "blue"];

    println!("{:?}", numbers); // [1, 2, 3]
    println!("{:?}", colors);  // ["red", "green", "blue"]
}

よく使う操作

fn main() {
    let mut v = vec![10, 20, 30, 40, 50];

    // 長さ
    println!("len: {}", v.len()); // 5

    // アクセス(インデックスは usize)
    let i: usize = 0;
    println!("first: {}", v[i]); // 10

    // 追加
    v.push(60);

    // 最後を削除
    v.pop();

    // ループ
    for item in v.clone() {
        println!("{}", item);
    }

    // まだ v を使える(clone したから)
    println!("{:?}", v);
}

for item in v とすると v が消費されて以降使えなくなる.v.clone() を渡せば OK.

イテレータメソッド

Vec にはループの代わりに使えるメソッドがたくさんある.他の言語で配列の mapfilter を使ったことがあるなら馴染みがあるはず.

まず前提として,クロージャ(無名関数)の書き方を知っておく必要がある:

|x| x + 1          // 引数 x を受け取って x + 1 を返す
|x, y| x + y       // 引数 2 つ
|x| { x * 2 }      // 中括弧で囲んでもいい
|x| x % 2 == 0     // bool を返すのもOK

語弊注意: クロージャには「環境をキャプチャする」という重要な性質があるけど,今は「その場で書ける短い関数」くらいの理解で OK.

map: 各要素を変換する

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];

    let doubled: Vec<i32> = numbers
        .iter()
        .map(|x| x * 2)
        .collect();

    println!("{:?}", doubled); // [2, 4, 6, 8, 10]
}

.iter() で各要素を順に取り出し,.map() で変換し,.collect() で Vec に戻す.この 3 点セットが基本パターン.

.iter() は「要素をちょっと覗くだけ」のイテレータを作る.元の Vec は消費されないので clone 不要.

filter: 条件に合う要素だけ残す

fn main() {
    let numbers = vec![1, 2, 3, 4, 5, 6];

    let evens: Vec<&i32> = numbers
        .iter()
        .filter(|x| *x % 2 == 0)
        .collect();

    println!("{:?}", evens); // [2, 4, 6]
}

filter のクロージャは &&i32(参照の参照)を受け取る.**x と書くか,|&&x| とパターンで受けるか,*x と書くか……正直ややこしい.コンパイラのエラーメッセージに従って * を足し引きすれば大体うまくいく.

map と filter のチェーン

fn main() {
    let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    // 偶数だけ取り出して 2 倍にする
    let result: Vec<i32> = numbers
        .iter()
        .filter(|x| *x % 2 == 0)
        .map(|x| x * 2)
        .collect();

    println!("{:?}", result); // [4, 8, 12, 16, 20]
}

メソッドチェーンで繋げていく.読みやすい.

enumerate: インデックス付きでループ

fn main() {
    let fruits = vec!["apple", "banana", "cherry"];

    for (i, fruit) in fruits.iter().enumerate() {
        println!("{}: {}", i, fruit);
    }
    // 0: apple
    // 1: banana
    // 2: cherry
}

enumerate()(usize, &要素) のタプルを返す.

find: 条件に合う最初の要素を探す

fn main() {
    let numbers = vec![1, 3, 5, 8, 10, 13];

    let first_even = numbers.iter().find(|x| *x % 2 == 0);

    match first_even {
        Some(n) => println!("found: {}", n), // found: 8
        None => println!("not found"),
    }
}

findOption を返す.見つからなければ None

any / all: 条件チェック

fn main() {
    let numbers = vec![2, 4, 6, 8];

    let has_odd = numbers.iter().any(|x| x % 2 != 0);
    println!("has odd: {}", has_odd); // false

    let all_positive = numbers.iter().all(|x| *x > 0);
    println!("all positive: {}", all_positive); // true
}

sum / count

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];

    let total: i32 = numbers.iter().sum();
    println!("sum: {}", total); // 15

    let how_many = numbers.iter().filter(|x| *x > 3).count();
    println!("count > 3: {}", how_many); // 2
}

sum() は型注釈(: i32)が必要なことが多い.コンパイラが「何の型に合計するか分からない」と言ってきたら型を書こう.

まとめ: よく使うイテレータメソッド

メソッド やること 返り値
.iter() イテレータを作る(元を消費しない) Iterator
.map(|x| ...) 各要素を変換 Iterator
.filter(|x| ...) 条件に合う要素だけ残す Iterator
.collect() イテレータを Vec 等に変換 Vec など
.enumerate() インデックスを付ける Iterator
.find(|x| ...) 最初に条件を満たす要素 Option
.any(|x| ...) 条件を満たす要素があるか bool
.all(|x| ...) 全要素が条件を満たすか bool
.sum() 合計 数値型
.count() 要素数を数える usize

for ループで書いても同じことはできるけど,イテレータメソッドで書くと意図が明確になる.Rust では頻繁に使うので慣れておこう.

Vec と String も「動的なやつ」

fn main() {
    let a = vec![1, 2, 3];
    let b = a;
    // println!("{:?}", a); // エラー!
    println!("{:?}", b);    // OK
}

String と同じルール.clone で解決.

HashMap: キーと値のペア

おまけ.他の言語の辞書やマップに相当:

use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();
    scores.insert(String::from("Alice"), 100);
    scores.insert(String::from("Bob"), 85);

    // 取得
    match scores.get("Alice") {
        Some(score) => println!("Alice: {}", score),
        None => println!("not found"),
    }

    // ループ
    for (name, score) in scores.clone() {
        println!("{}: {}", name, score);
    }
}

use std::collections::HashMap; を先頭に書くのを忘れずに.getOption を返す — これは次の章で説明する.

まとめ

  • i32, f64, bool → コピーされる.気にしなくていい
  • String, Vec, HashMap → 渡すと消える.困ったら .clone()
  • 深い理由は今は考えない

次の章で OptionResult を学べば,Rust の基礎は完了.いよいよ電卓を作れるようになる.

関連コンテンツ

本に戻る