🦀

Rustの勉強 8日目

2022/10/05に公開

The Rust Programming Language 日本語版 第8章

いよいよコレクション型についての章。楽しみ。

8.1 ベクタで値のリストを保持する

  • ベクタ型 Vec<T> は要素のリストがある場合に便利
  • vec! マクロで初期化すると同時にジェネリクスの型を推論できる( let v = vec![1, 2, 3];
  • ベクタへの要素の追加は push メソッド
  • ベクタは drop されると全要素が drop されると全要素が
  • ベクタの要素にアクセスするにはカギカッコか get メソッドに添え字を渡す
    • get メソッドでは要素がなかった場合には None を返す。カギカッコの場合はパニックする。(Pythonとかと同じ)
  • for ループで全要素にアクセスする場合には参照を取得する必要がある

8.2 文字列でUTF-8でエンコードされたテキストを保持する

  • 文字列型はプリミティブには str しか存在しない。その借用の &str を見ることが多い。
  • 標準ライブラリにある String は伸長可能、ミュータブル、所有権のあるUTF-8エンコードされた文字列。
  • String::from()to_string は同じ
  • String には push_strpush というメソッドがあり、前者は str を、後者は文字を受け取って、 String に追加する。このとき渡している変数の所有権は取らない。
  • String の結合はふるまいとしてはまりそう。 + 演算子の左辺は所有権が取られるのに対し、右辺は参照を渡すだけなので所有権が取られない。
    • このとき型強制というのが働いて、下のコードは String + &str の扱いになる
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // s1はムーブされ、もう使用できないことに注意
  • format! マクロは Go で言うところの fmt.Sprintf
  • String は添字でその場所のバイトの値を返すことはしない
    • しかし範囲を使って部分文字列にアクセスはできる。( let s = &hello[0..4];
    • chars()bytes() メソッドを使える
    • chars() は書紀素クラスタを返すわけではないので文字としてみたらおかしな値になるかもしれない
    • Goでも書紀素クラスタは上手く扱えない(c.f. https://go.dev/play/p/m0mMaWNx1wS

8.3 キーとそれに紐づいた値をハッシュマップに格納する

  • HashMap<K, V> はキーと値のペアを持つ、他の言語でも辞書やマップなどと呼ばれるデータ型
  • std::collections::HashMap::new で作れる
    • insert メソッドで要素を上書き追加
    • entry メソッドから or_insert メソッドを使うと要素が存在しなかったときだけ追加する
  • ハッシュマップにキーや値としてヒープに値を保持する変数を渡すと所有権が移るので注意

Rustlings

vecs、strings、hashmaps が対応している章なので回答していく。

vecs1.rs

テストの中身がベクタの中身の配列を取って比較していたので、同じ値を持つようなベクタを vec! マクロで生成

fn array_and_vec() -> ([i32; 4], Vec<i32>) {
    let a = [10, 20, 30, 40]; // a plain array
    let v = vec![10, 20, 30, 40];

    (a, v)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_array_and_vec_similarity() {
        let (a, v) = array_and_vec();
        assert_eq!(a, v[..]);
    }
}

vecs2.rs

iter_mut が何を返すか良くわからなかったのでドキュメントを参照した。そしたら IterMut 構造体を返すっぽいけど、なぜこれが for&mut を返してくるのか良くわからなかったので、雰囲気で AsRef トレイトがその辺よしなにやってると予想して雰囲気で書いたら合ってた。

fn vec_loop(mut v: Vec<i32>) -> Vec<i32> {
    for i in v.iter_mut() {
        *i *= 2;
    }
    v
}

fn vec_map(v: &Vec<i32>) -> Vec<i32> {
    v.iter().map(|num| num * 2).collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_vec_loop() {
        let v: Vec<i32> = (1..).filter(|x| x % 2 == 0).take(5).collect();
        let ans = vec_loop(v.clone());

        assert_eq!(ans, v.iter().map(|x| x * 2).collect::<Vec<i32>>());
    }

    #[test]
    fn test_vec_map() {
        let v: Vec<i32> = (1..).filter(|x| x % 2 == 0).take(5).collect();
        let ans = vec_map(&v);

        assert_eq!(ans, v.iter().map(|x| x * 2).collect::<Vec<i32>>());
    }
}

strings1.rs

current_favorite_colorstr を返してたので String を返すように修正した

fn main() {
    let answer = current_favorite_color();
    println!("My current favorite color is {}", answer);
}

fn current_favorite_color() -> String {
    String::from("blue")
}

strings2.rs

&str を渡すところに String を渡してたのを参照にすることで型強制によって &str にさせた

fn main() {
    let word = String::from("green"); // Try not changing this line :)
    if is_a_color_word(&word) {
        println!("That is a color word I know!");
    } else {
        println!("That is not a color word I know.");
    }
}

fn is_a_color_word(attempt: &str) -> bool {
    attempt == "green" || attempt == "blue" || attempt == "red"
}

strings3.rs

std::str::String::trim 相当のものを自力で実装しようとしていてはまっていた。

fn trim_me(input: &str) -> String {
    let s = String::from(input);
    String::from(s.trim())
}

fn compose_me(input: &str) -> String {
    String::from(input) + " world!"
}

fn replace_me(input: &str) -> String {
    // TODO: Replace "cars" in the string with "balloons"!
    String::from(input).replace("cars", "balloons")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn trim_a_string() {
        assert_eq!(trim_me("Hello!     "), "Hello!");
        assert_eq!(trim_me("  What's up!"), "What's up!");
        assert_eq!(trim_me("   Hola!  "), "Hola!");
    }

    #[test]
    fn compose_a_string() {
        assert_eq!(compose_me("Hello"), "Hello world!");
        assert_eq!(compose_me("Goodbye"), "Goodbye world!");
    }

    #[test]
    fn replace_a_string() {
        assert_eq!(
            replace_me("I think cars are cool"),
            "I think balloons are cool"
        );
        assert_eq!(
            replace_me("I love to look at cars"),
            "I love to look at balloons"
        );
    }
}

strings4.rs

概ねどの操作が &strString のどちらを返すかなんとなく想像できたが、全部を把握するにはまだ修業が必要。

fn string_slice(arg: &str) {
    println!("{}", arg);
}
fn string(arg: String) {
    println!("{}", arg);
}

fn main() {
    string_slice("blue");
    string("red".to_string());
    string(String::from("hi"));
    string("rust is fun!".to_owned());
    string_slice("nice weather".into());
    string(format!("Interpolation {}", "Station"));
    string_slice(&String::from("abc")[0..1]);
    string_slice("  hello there ".trim());
    string("Happy Monday!".to_string().replace("Mon", "Tues"));
    string("mY sHiFt KeY iS sTiCkY".to_lowercase());
}

hashmaps1.rs

ハッシュマップの新規作成のときに型の指定の必要がないのかと一瞬戸惑ってしまった

use std::collections::HashMap;

fn fruit_basket() -> HashMap<String, u32> {
    let mut basket = HashMap::new();
    // Two bananas are already given for you :)
    basket.insert(String::from("banana"), 2);
    basket.insert(String::from("lemon"), 3);
    basket.insert(String::from("apple"), 5);
    basket
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn at_least_three_types_of_fruits() {
        let basket = fruit_basket();
        assert!(basket.len() >= 3);
    }

    #[test]
    fn at_least_five_fruits() {
        let basket = fruit_basket();
        assert!(basket.values().sum::<u32>() >= 5);
    }
}

hashmaps2.rs

ハッシュマップの中にキーが存在しているかどうかを確認するためには entry を使うというのを忘れないように

use std::collections::HashMap;

#[derive(Hash, PartialEq, Eq)]
enum Fruit {
    Apple,
    Banana,
    Mango,
    Lychee,
    Pineapple,
}

fn fruit_basket(basket: &mut HashMap<Fruit, u32>) {
    let fruit_kinds = vec![
        Fruit::Apple,
        Fruit::Banana,
        Fruit::Mango,
        Fruit::Lychee,
        Fruit::Pineapple,
    ];

    for fruit in fruit_kinds {
        basket.entry(fruit).or_insert(1);
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn get_fruit_basket() -> HashMap<Fruit, u32> {
        let mut basket = HashMap::<Fruit, u32>::new();
        basket.insert(Fruit::Apple, 4);
        basket.insert(Fruit::Mango, 2);
        basket.insert(Fruit::Lychee, 5);

        basket
    }

    #[test]
    fn test_given_fruits_are_not_modified() {
        let mut basket = get_fruit_basket();
        fruit_basket(&mut basket);
        assert_eq!(*basket.get(&Fruit::Apple).unwrap(), 4);
        assert_eq!(*basket.get(&Fruit::Mango).unwrap(), 2);
        assert_eq!(*basket.get(&Fruit::Lychee).unwrap(), 5);
    }

    #[test]
    fn at_least_five_types_of_fruits() {
        let mut basket = get_fruit_basket();
        fruit_basket(&mut basket);
        let count_fruit_kinds = basket.len();
        assert!(count_fruit_kinds >= 5);
    }

    #[test]
    fn greater_than_eleven_fruits() {
        let mut basket = get_fruit_basket();
        fruit_basket(&mut basket);
        let count = basket.values().sum::<u32>();
        assert!(count > 11);
    }
}

hashmaps3.rs

チームがすでに scores に存在していた場合の点数の更新のところで、 t.name.clone() としたのが正しいかよくわからない

use std::collections::HashMap;

// A structure to store team name and its goal details.
struct Team {
    name: String,
    goals_scored: u8,
    goals_conceded: u8,
}

fn build_scores_table(results: String) -> HashMap<String, Team> {
    // The name of the team is the key and its associated struct is the value.
    let mut scores: HashMap<String, Team> = HashMap::new();

    for r in results.lines() {
        let v: Vec<&str> = r.split(',').collect();
        let team_1_name = v[0].to_string();
        let team_1_score: u8 = v[2].parse().unwrap();
        let team_2_name = v[1].to_string();
        let team_2_score: u8 = v[3].parse().unwrap();

        match scores.get(&team_1_name) {
            None => scores.insert(
                String::from(&team_1_name),
                Team {
                    name: String::from(&team_1_name),
                    goals_scored: team_1_score,
                    goals_conceded: team_2_score,
                },
            ),
            Some(t) => scores.insert(
                t.name.clone(),
                Team {
                    name: t.name.clone(),
                    goals_scored: t.goals_scored + team_1_score,
                    goals_conceded: t.goals_conceded + team_2_score,
                },
            ),
        };

        match scores.get(&team_2_name) {
            None => scores.insert(
                String::from(&team_2_name),
                Team {
                    name: String::from(&team_2_name),
                    goals_scored: team_2_score,
                    goals_conceded: team_1_score,
                },
            ),
            Some(t) => scores.insert(
                t.name.clone(),
                Team {
                    name: t.name.clone(),
                    goals_scored: t.goals_scored + team_2_score,
                    goals_conceded: t.goals_conceded + team_1_score,
                },
            ),
        };
    }
    scores
}

#[cfg(test)]
mod tests {
    use super::*;

    fn get_results() -> String {
        let results = "".to_string()
            + "England,France,4,2\n"
            + "France,Italy,3,1\n"
            + "Poland,Spain,2,0\n"
            + "Germany,England,2,1\n";
        results
    }

    #[test]
    fn build_scores() {
        let scores = build_scores_table(get_results());

        let mut keys: Vec<&String> = scores.keys().collect();
        keys.sort();
        assert_eq!(
            keys,
            vec!["England", "France", "Germany", "Italy", "Poland", "Spain"]
        );
    }

    #[test]
    fn validate_team_score_1() {
        let scores = build_scores_table(get_results());
        let team = scores.get("England").unwrap();
        assert_eq!(team.goals_scored, 5);
        assert_eq!(team.goals_conceded, 4);
    }

    #[test]
    fn validate_team_score_2() {
        let scores = build_scores_table(get_results());
        let team = scores.get("Spain").unwrap();
        assert_eq!(team.goals_scored, 0);
        assert_eq!(team.goals_conceded, 2);
    }
}

Discussion