Closed15
The Rust Programming Language - Chapter 8 メモ
一般的なコレクション
- ベクタ:可変長の値を並べて保持できる
- 文字列:文字のコレクション
- ハッシュマップ:値を特定のキーと紐付け。マップの特定の実装。
ベクタで一連の値を保持する
ベクタは同じ型の値を保持するコレクション
// 空のベクタを生成、Vec<T> の型注釈が必要
let v: Vec<i32> = Vec::new();
// vec! により値を含むベクタを生成
let v = vec![1, 2, 3];
ベクタを更新する
rust
// push 可能にするには mut キーワードが必要
let mut v = vec![1, 2, 3];
v.push(4); // OK
let v = vec![1, 2, 3];
v.push(4); // NG
TypeScript
// ts は let でも const でも配列の要素の追加削除はできてしまう。
let v = [1, 2, 3];
v.push(4); // OK
const v = [1, 2, 3];
v.push(4); // OK
ベクタをドロップすれば、要素もドロップする
- ベクタがドロップされると、その中身もドロップされる。
- Vec<T> の T が struct の場合どうなる??
ベクタの要素を読む
rust
let mut v = vec![1, 2, 3, 4, 5];
let third: &i32 = &v[2];
let ten: &i32 = &v[9]; // panic
// ベクタの範囲外にアクセスする可能性があるときは get を使う
let third: Option<&i32> = v.get(10); // None
TypeScript
const v = [1, 2, 3, 4, 5];
const third = v[2];
// out of range の要素にアクセスした場合は undefined が返るだけ
const ten = v[9] // undefined
- 不変借用がある場合、可変で借用できない
Rust
let mut v = vec![1, 2, 3, 4, 5];
let first = &v[0];
v.push(6); // NG
新規要素をベクタの終端に追加すると、ベクタが現在存在する位置に隣り合って要素を入れるだけの領域がなかった場合に、 メモリの新規確保をして古い要素を新しいスペースにコピーする必要があるかもしれないからです。 その場合、最初の要素を指す参照は、解放されたメモリを指すことになるでしょう。借用規則により、 そのような場面に陥らないよう回避されるのです。
なるほど。
ベクタの値を走査する
Rust
let mut v = vec![100, 32, 57];
for i in &mut v {
// 参照外し演算子(*)をつけないといけない
*i += 50;
}
println!("{:?}", v); // [150, 82, 107]
TypeScript
// TS でこう書いても配列の中身は変わらない
const v = [100, 32, 57];
for (const i of v) {
i += 50;
}
console.log(v); // [100, 32, 57]
// やるならこう(Rust のほうが簡潔)
const v = [100, 32, 57];
for (let i = 0, len = v.length; i < len; i+) {
v[i] += 50;
}
console.log(v); // [150, 82, 107]
Enum を使って複数の型を保持する
一応こういうこともできるよ、という話。
Rust
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];
文字列でUTF-8でエンコードされたテキストを保持する
新参者は以下でよく行き詰まる
- Rustのありうるエラーを晒す性質
- 多くのプログラマが思っている以上に文字列が複雑なデータ構造であること
- そしてUTF-8
文字列とは
- Rust言語の核には文字列型は1種類しかない
-
str
:文字列スライス- 別の場所に格納されたUTF-8エンコードされた文字列データへの参照
- 文字列リテラルは、ブログラムのバイナリに出力されるので、文字列スライス
-
$str
:通常借用された形態
-
- 一方、
String
型は、Rustの標準ライブラリで提供される。- 伸長可能
- 可変
- 所有権のあるUTF-8エンコードされた文字列型
-
文字列
といえば、String
と&str
を意味する。 - Rustの標準ライブラリには、他の文字列型も存在する。
OsString
OsStr
CsString
CsStr
- 末尾が
String
orStr
で終わっているのは、前者が所有権あり、後者が借用されたバージョンを指す-
String
と&str
のようなもの
-
文字列型とString型は異なるもの
文字列といえば String と &str を意味する
新規文字列を生成する
Rust
// &str
let data = "initial contents";
// String型
let s = data.to_string();
let s = "initial contents".to_string();
let s = String::from("initial contents");
to_string()
、String::from
は同じこと。どちらを使うかはスタイル次第。
文字列を更新する
Rust
let mut s1 = String::from("foo");
let s2 = "bar";
// s2は所有権を奪われない
s1.push_str(s2);
println!("s2 is {}", s2); // bar
+演算子、またはformat!マクロで連結
Rust
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
// s1は所有権を奪われる
// s2は所有権を奪われない
// + 演算子を使用した時に add メソッドがポイント
let s3 = s1 + &s2;
// s1.add が s2 を &String -> &str に型強制する
// self には & がついていないので s1 は add を読んだらムーブされる
fn add(self, s: &str) -> String {
format!
マクロを使う
Rust
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
// add では見づらいし、s1 はムーブされてしまう
let s = s1 + "-" + &s2 + "-" + &s3;
// format! を使えば、見やすく、かつs1もムーブされない
let s = format!("{}-{}-{}", s1, s2, s3);
文字列に添字アクセスする
String
は Vec<u8>
のラッパー。だけど添字アクセスは良くない!
Rust
// 期待通りになる
let hello = String::from("Hola")
let len = hello.len(); // 4
let first = &hello[0]; // H
// 期待通りにならない
let hello = String::from("Здравствуйте");
let len = hello.len(); // 12 ではなく 24!
let first = &hello[0]; // 3 ではない!
文字列をスライスする
Rust
let hello = "Здравствуйте";
let s = &hello[0..4]; // Зд
// 範囲を使用して文字列スライスを作る際には、プログラムをクラッシュさせることがある
let s = &hello[0..1]; // panic
文字列を走査するメソッド群
Rust
// char() を使えば、ちゃんと1文字ずつ処理することができる
for c in "नमस्ते".chars() {
println!("{}", c);
}
Rust
// bytes() もうまくいくこともあるかもしれないが、Unicodeは2バイト以上からなることもあるので注意が必要
for b in "नमस्ते".bytes() {
println!("{}", b);
}
文字列はそう簡単じゃない
- 文字列は込み入っている
- Rustでは
String
データを正しく扱うことに注意が必要- UTF-8を素直に扱うより複雑
- 一方で、ASCII位ギアの文字に対するエラーの可能性が排除されている
キーとそれに紐づいた値をハッシュマップに格納する
新規ハッシュマップを生成する
Rust
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
// ベクターからハッシュマップへの変換
let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
// zip すると タプルのベクターができる
let teams_and_ teams.iter().zip(initial_scores.iter()); // [("Blue", 10), ("Yellow", 50)]
// collect() を使って HashMap の型を明示してハッシュマップに変換する、型推論できるので _ でOK
let scores: HashMap<_, _> = teams_and_.collect();
ハッシュマップと所有権
Rust
use std::collections::HashMap;
let field_name = String::from("Favorite color");
let field_value = String::from("Blue");
let mut map = HashMap::new();
// field_name も field_value もムーブされて、以降使えない
map.insert(field_name, field_value);
ハッシュマップの値にアクセスする
Rust
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
let team_name = String::from("Blue");
// get は Option<&v> を返す
let score = scores.get(&team_name); // 10 ではなく Some(10)
// for で走査する
for (key, value) in &scores {
// Yellow: 50
// Blue: 10
println!("{}: {}", key, value);
}
ハッシュマップを更新する
値の上書き
Rust
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25);
// Blue というキーしかないので
println!("{:?}", scores); // {"Blue": 25}
キーに値がなかった時のみ値を挿入する
Rust
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
// entry, or_insert で存在チェックし、なければ挿入する
scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(50);
// Blue の値が 50 に上書きされていない
println!("{:?}", scores); // {"Blue": 10, "Yellow": 50}
古い値に基づいて値を更新する
Rust
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
// or_insert() は値への可変参照(&mut V)を返す
// 最初はキーが存在していないので 0 がセットされ、その値を返す
// 2回目以降すでにキーが存在している場合は、0ではなくセットされた値を返す
let count = map.entry(word).or_insert(0);
// or_insert() により可変参照が返るので、その値を更新できる
*count += 1;
}
println!("{:?}", map); // {"world": 2, "hello": 1, "wonderful": 1}
このスクラップは2021/07/10にクローズされました