🦀
Rustの勉強 8日目
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_str
とpush
というメソッドがあり、前者は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_color
が str
を返してたので 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
概ねどの操作が &str
と String
のどちらを返すかなんとなく想像できたが、全部を把握するにはまだ修業が必要。
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