The Rust Programming Language 要約 3-8章
https://doc.rust-jp.rs/book/second-edition/
3. 普遍的なプログラミング概念
3.1. 変数と可変性
変数は不変
let x = 5; x = 6; // Error!
mut を付けると可変に → 変数の型を見ると.変更されるかどうかが分かる.
let mut x = 5; x = 6; // OK
3.2. データ型
- 整数の符号あり/なし,サイズを区別して扱う → それぞれ別な型
- i8, u8, …, i64, u64, isize, usize
- 浮動小数点は f32 と f64
Rustのchar型は,ユニコード のスカラー値を表す.
let c = '乃';
タプルがある
let tup: (i32, f64, u8) = (500, 6.4, 1); // tup.0 → 500
配列の範囲外にアクセスすると,正しく落ちる
fn main() { let a = [1, 2, 3, 4, 5]; let index = 10; let element = a[index]; println!("The value of element is: {}", element); }
3.3. 関数の動作法
- 関数の最後に評価した式が戻り値 (return をあえて書かない)
セミコロンは,文の区切りなので最後の文には不要.→ 付けると,最後に空文が入っていると解釈される.
fn plus_one_NG(x: i32) -> i32 { x + 1; // ; のせいで最後に空タプル()がある→ 型違いで Error! } fn plus_one_OK(x: i32) -> i32 { x + 1 // OK }
3.4. コメント
- // が使えるよ
3.5. 制御フロー
- if, loop, while, for が使える
if は,Ruby のように値を返す
let number = if condition { 5 } else { 6 };
for は,イテレータを取る
fn main() { let a = [10, 20, 30, 40, 50]; for element in a.iter() { println!("the value is: {}", element); } }
4. 所有権を理解する
4.1. 所有権とは?
- スタックとヒープ
- プログラムがオブジェクト(変数)を利用するためには,メモリの確保,解放が必要
- スタック領域
- ○ 確保,解放が 高速.スコープと連動しやすい
- × 大きなサイズのオブジェクトを確保できない
- × 動的にサイズが変わるオブジェクトには向かない
- ヒープ領域は,その逆
変数のスコープ
文字列リテラル はプログラムのテキストとしてハードコードされている.
{ // sは、ここでは有効ではない。まだ宣言されていない let s = "hello"; // sは、ここから有効になる // sで作業をする } // s は無効
可変な文字列 String 型を考える:
let mut s = String::from("hello"); s.push_str(", world!"); // push_str()関数は、リテラルをStringに付け加える println!("{}", s); // これは`hello, world!`と出力する
メモリと確保
- メモリは,実行時にOSに要求
- 全ての言語で同じ
- String 型を使用し終わったら,OSにこのメモリを返還する方法が必要
- 頑張ってプログラマが free を書く → メモリリークや dangling pointer の危険
- GC (Garbage Collection) → プログラムが遅くなる,メモリ周りの制御ができない
- Rust: スコープを抜けたら自動で free (Rust では drop) → RAII (Resource Acquisition is Initialization)
- C++ でもあるよね? → C++ は,dangling pointer (lifetime, ownership) の問題を解決しようと思うと大変! ∵ Cの呪縛があるから
{ let s = String::from("hello"); // sはここから有効になる // sで作業をする } // このスコープはここでおしまい。sは // もう有効ではない // → メモリを返さなきゃ!
- メモリは,実行時にOSに要求
変数とデータの相互作用: ムーブ
代入ってコピーだよね?
let x = 5; let y = x; // これはコピー ∵ コピーのコストが低いから
- メモリ上での大きさが固定で単純なコピーで済む値 → コピーされる
- Rust では,スタック上に置ける固定長の値は,
- 代入によってコピーされる
- スコープを抜けると自動で drop される
- これは,他の言語でも同じ.Java でいう,primitive 型のイメージ.
では,可変長の文字列 String は? (可変な長さを持つ → スタックには積めない)
let s1 = String::from("hello");
下のようになる.左は スタック,右は ヒープ 上に存在する.
図1: s1に束縛された"hello"という値を保持するStringのメモリ上の表現
これを s2 に代入しても以下のようにはならない (Deep Copy しない)
let s1 = String::from("hello"); let s2 = s1; // コピーなのか?
図2: Rust がもしヒープデータもコピーするとした場合の s2 = s1 の結果
C脳なら,s2 に代入するとこんなイメージ? (Shallow Copy)
図3: s1のポインタ、長さ、許容量のコピーを保持する変数s2のメモリ上での表現
しかし,これは問題:
- 2個所から文字列の本体(ヒープ)にある → メモリをいつ解法すればいい?
- s1 と s2 のどちらがスコープを抜けたときに drop すればいい?
- 関数の引数に渡す (一種のコピー) したらどうなるの?
→ Rust では,代入によって,s1 を無効化することで解決: 所有権の移動 (move) という
let s1 = String::from("hello"); let s2 = s1; // 所有権が s2 に move.s1 は以降参照できない
図4: s1が無効化された後のメモリ表現
以下のコードをコンパイルしようとすると,
let s1 = String::from("hello"); let s2 = s1; println!("{}, world!", s1);
こうなる.
error[E0382]: use of moved value: `s1` (ムーブされた値の使用: `s1`) --> src/main.rs:5:28 | 3 | let s2 = s1; | -- value moved here 4 | 5 | println!("{}, world!", s1); | ^^ value used here after move | (ムーブ後にここで使用されています) | = note: move occurs because `s1` has type `std::string::String`, which does not implement the `Copy` trait (注釈: ムーブが起きたのは、`s1`が`std::string::String`という `Copy`トレイトを実装していない型だからです)
- 変数とデータの相互作用法: クローン
deepy Copy が必要なら,clone() を使え
let s1 = String::from("hello"); let s2 = s1.clone(); println!("s1 = {}, s2 = {}", s1, s2);
この場合は,こうなる
図5: Rust がもしヒープデータもコピーするとした場合の s2 = s1 の結果
- スタックのみのデータ: コピー
- スタックの中だけに収まるデータは,「Copy トレイトを実装している」 (Copy 可能なオブジェクトのクラスみたいなイメージ)
- あらゆる整数型.u32など.
- 論理値型,bool,true/false という値がある.
- あらゆる浮動小数点型,f64など.
- 文字型、char.
- タプル.ただ Copy の型だけを含む場合.例えば,(i32, i32) は,Copyだが,(i32, String) は,違う.
- スタックの中だけに収まるデータは,「Copy トレイトを実装している」 (Copy 可能なオブジェクトのクラスみたいなイメージ)
- 戻り値とスコープ
関数呼出しで渡したり返したりしても所有権が移動する
fn main() { let s1 = String::from("hello"); // s1 の所有権を関数に move し,s2 として戻してもらっている!! let (s2, len) = calculate_length(s1); println!("The length of '{}' is {}.", s2, len); } fn calculate_length(s: String) -> (String, usize) { let length = s.len(); // len() メソッドは、Stringの長さ (s, length) // length だけ戻せばいいのに…面倒すぎる… // s を戻さないと,ここで s は Drop (破棄) される }
→ 一時的に s を覗けるようにしたい → 参照と借用の考え方
4.2. 参照と借用
参照と借用
所有権を渡さないで参照だけを渡す
fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); println!("The length of '{}' is {}.", s1, len); } fn calculate_length(s: &String) -> usize { // sはStringへの参照 s.len() } // ここで、sはスコープ外になる。けど、参照しているものの所有権を持っているわけではないので // 何も起こらない
こんなイメージ
図6: String s1を指す&String sの図表
可変な参照
こう書ける:
fn main() { let mut s = String::from("hello"); change(&mut s); } fn change(some_string: &mut String) { some_string.push_str(", world"); }
しかし, 可変な参照は同時に1つしか持てない ← 重要
- 不変な参照(借用)は同時にいくつも持てる
- 可変な参照(借用)は同時に1つしか持てない(不変可変にかかわらず他の参照があってはならない)
宙に浮いた参照 (dangling pointer)
これはやばい:
fn main() { let reference_to_nothing = dangle(); } fn dangle() -> &String { // dangleはStringへの参照を返す let s = String::from("hello"); // sは新しいString &s // String sへの参照を返す } // ここで、sはスコープを抜け、ドロップされる。そのメモリは消される。 // 危険だ (本体が drop されているのに参照を返すのは危険)
なので,Rust では,コンパイルできない:
error[E0106]: missing lifetime specifier (エラー: ライフタイム指定子がありません) --> main.rs:5:16 | 5 | fn dangle() -> &String { | ^ expected lifetime parameter | = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from (助言: この関数の戻り値型は、借用した値を含んでいますが、借用される値がどこにもありません) = help: consider giving it a 'static lifetime ('staticライフタイムを与えることを考慮してみてください)
ライフタイムの話は,10章で詳しく.
こう書くと問題ない.(∵ 所有権ごと呼出し元に渡すから)
fn no_dangle() -> String { let s = String::from("hello"); s }
s は,呼出し元で Drop してくれるだろう.
4.3. スライス
文字列スライス
文字列の一部を借用する
&str
型let s = String::from("hello world"); let hello = &s[0..5]; let world = &s[6..11];
メモリ中で起こっていること:
図7: Stringオブジェクトの一部を参照する文字列スライス
&str
型を返却するfirst_word
関数:fn first_word(s: &String) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] }
この関数を呼び出す main を書いてみる:
fn main() { let mut s = String::from("hello world"); let word = first_word(&s); // word は s を不変借用 s.clear(); // error! 可変借用できない! println!("{}", word); }
clear 関数の定義: https://doc.rust-lang.org/std/string/struct.String.html#method.clear
ルールを思い出そう
- 不変な参照(借用)は同時にいくつも持てる
- 可変な参照(借用)は同時に1つしか持てない(不変可変にかかわらず他の参照があってはならない)
つまり,
- word が s を不変借用しているのに,s を clear に可変で借用させることはできない
→ 危まって無効な参照を使ってしまうことを事前に防げた.
引数としての文字列スライス
first_word
は,fn first_word(s: &String) -> &str {
とせずに,
fn first_word(s: &str) -> &str {
としたほうが汎用性が高い:
fn main() { let my_string = String::from("hello world"); // first_wordは`String`のスライスに対して機能する let word = first_word(&my_string[..]); let my_string_literal = "hello world"; // first_wordは文字列リテラルのスライスに対して機能する let word = first_word(&my_string_literal[..]); // 文字列リテラルは、すでに文字列スライス &str 型なので // スライス記法なしでも機能するのだ! let word = first_word(my_string_literal); }
5. 構造体を使用して関連のあるデータを構造化する
5.1. 構造体を定義し、インスタンス化する
1: // 定義 2: struct User { 3: username: String, 4: email: String, 5: sign_in_count: u64, 6: active: bool, 7: } 8: 9: // インスタンス化 10: let user1 = User { 11: email: String::from("someone@example.com"), 12: username: String::from("someusername123"), 13: active: true, 14: sign_in_count: 1, 15: }; 16: 17: // User を返す関数として定義 18: fn build_user(email: String, username: String) -> User { 19: User { 20: email: email, 21: username: username, 22: active: true, 23: sign_in_count: 1, 24: } 25: } 26: 27: // フィールドと変数が同名の時にフィールド初期化省略記法を使う 28: fn build_user(email: String, username: String) -> User { 29: User { 30: email, 31: username, 32: active: true, 33: sign_in_count: 1, 34: } 35: } 36: 37: // user1 の値を使いつつ user2 を作る 38: let user2 = User { 39: email: String::from("another@example.com"), 40: username: String::from("anotherusername567"), 41: active: user1.active, 42: sign_in_count: user1.sign_in_count, 43: }; 44: 45: // 構造体更新記法を使うと楽 46: let user2 = User { 47: email: String::from("another@example.com"), 48: username: String::from("anotherusername567"), 49: ..user1 50: }; 51: 52: // タプル構造体 (Color と Point は別の型) 53: struct Color(i32, i32, i32); 54: struct Point(i32, i32, i32); 55: struct Unit() // ユニット様構造体 56: 57: let black = Color(0, 0, 0); 58: let origin = Point(0, 0, 0);
5.2. 構造体を使用したプログラム例
このプログラムを構造体を使って綺麗にすると?
fn main() { let width1 = 30; let height1 = 50; println!( // 四角形の面積は、{}平方ピクセルです "The area of the rectangle is {} square pixels.", area(width1, height1) ); } fn area(width: u32, height: u32) -> u32 { width * height }
まず,タプルにしてみる
fn main() { let rect1 = (30, 50); println!( "The area of the rectangle is {} square pixels.", area(rect1) ); } fn area(dimensions: (u32, u32)) -> u32 { dimensions.0 * dimensions.1 }
構造体にすると
struct Rectangle { width: u32, height: u32, } fn main() { let rect1 = Rectangle { width: 30, height: 50 }; println!( "The area of the rectangle is {} square pixels.", area(&rect1) ); } fn area(rectangle: &Rectangle) -> u32 { rectangle.width * rectangle.height }
area 関数は,普通なら Rectangle クラスに属するメソッドだよね? (Rust にはクラスはないけどね)
println に 自作の構造体を表示させるには?
struct Rectangle { width: u32, height: u32, } fn main() { let rect1 = Rectangle { width: 30, height: 50 }; // rect1は{}です println!("rect1 is {}", rect1); // → 普通にやるとエラー }
Display トレイトを実装すると,表示されるようになるが,今回は,Debug トレイトで derive して我慢.
#[derive(Debug)] struct Rectangle { width: u32, height: u32, } fn main() { let rect1 = Rectangle { width: 30, height: 50 }; println!("rect1 is {:?}", rect1); // {} ではなく {:?} }
5.3. メソッド記法
前の節で書いた構造体記法:
struct Rectangle { width: u32, height: u32, } fn main() { let rect1 = Rectangle { width: 30, height: 50 }; println!( "The area of the rectangle is {} square pixels.", area(&rect1) ); } fn area(rectangle: &Rectangle) -> u32 { rectangle.width * rectangle.height }
area 関数は,普通なら Rectanble クラスに属するメソッドだよね? → こう書ける:
#[derive(Debug)] struct Rectangle { width: u32, height: u32, } impl Rectangle { // &self がミソ fn area(&self) -> u32 { self.width * self.height } fn can_hold(&self, other: &Rectangle) -> bool { self.width > other.width && self.height > other.height } } fn main() { let rect1 = Rectangle { width: 30, height: 50 }; println!( "The area of the rectangle is {} square pixels.", rect1.area() ); }
関連関数 (クラスメソッドっぽいもの) → self を引数に取らなければ,そうなる
impl Rectangle { fn square(size: u32) -> Rectangle { Rectangle { width: size, height: size } } } let sq = Rectangle::square(3);
6. Enumとパターンマッチング
6.1. Enumを定義する
普通の C言語っぽい Enum
enum IpAddrKind { V4, V6, } let four = IpAddrKind::V4; let six = IpAddrKind::V6; fn route(ip_type: IpAddrKind) {}
→ V4 or V6 の区別にしか使えないので,構造体と組合せるはめに:
enum IpAddrKind { V4, V6, } struct IpAddr { kind: IpAddrKind, address: String, } let home = IpAddr { kind: IpAddrKind::V4, address: String::from("127.0.0.1"), }; let loopback = IpAddr { kind: IpAddrKind::V6, address: String::from("::1"), };
→ IPv4型, IPv6 型でポリモフィックにしたいときに困るよね
if kind == IpAddrKind::V4 ...
みたいなコードが随所に出るのは嫌.
こう書ける:
enum IpAddr { V4(String), V6(String), } let home = IpAddr::V4(String::from("127.0.0.1")); let loopback = IpAddr::V6(String::from("::1"));
どちらの address も String である必要はない.
enum IpAddr { V4(u8, u8, u8, u8), V6(String), } let home = IpAddr::V4(127, 0, 0, 1); let loopback = IpAddr::V6(String::from("::1"));
こう書くのが Rust 流:
struct Ipv4Addr { // 省略 } struct Ipv6Addr { // 省略 } enum IpAddr { V4(Ipv4Addr), V6(Ipv6Addr), }
Enum の別の例.
enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), }
名簿管理プログラムの Command 型を作るとすると,これに似た感じになりそう.
enum 自体にメソッドを追加できる:
enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), } impl Message { fn call(&self) { // method body would be defined here // メソッド本体はここに定義される } } let m = Message::Write(String::from("hello")); m.call();
標準ライブラリでの Enum の例: Option型
enum Option<T> { Some(T), None, }
こんな感じで使う
fn plus_one(x: Option<i32>) -> Option<i32> { match x { None => None, Some(i) => Some(i + 1), } } let five = Some(5); let six = plus_one(five); let none = plus_one(None);
別の例.エラーを前提とする Result 型
enum Result<T, E> { Ok(T), Err(E), }
Result を返すことで,C でありがちな NULL の悲劇がなくなる.
use std::fs::File; fn main() { // File::open は,Result<std::fs::File, std::io::Error> 型の値を返すので, let f = File::open("hello.txt"); let f = match f { // f が Ok(T) なら,成功した結果の file を取り出す Ok(file) => file, // f が Error(E) なら終了 Err(error) => { panic!("There was a problem opening the file: {:?}", error) }, }; }
6.2. match制御フロー演算子
match … switch のすごいやつ
enum Coin { Penny, Nickel, Dime, Quarter, } fn value_in_cents(coin: Coin) -> u32 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter => 25, } }
Option<T>とのマッチ
fn plus_one(x: Option<i32>) -> Option<i32> { match x { None => None, Some(i) => Some(i + 1), } } let five = Some(5); let six = plus_one(five); let none = plus_one(None);
-
let some_u8_value = 0u8; match some_u8_value { 1 => println!("one"), 3 => println!("three"), 5 => println!("five"), 7 => println!("seven"), _ => (), }
6.3. if letで簡潔な制御フロー
let some_u8_value = Some(0u8); match some_u8_value { Some(3) => println!("three"), _ => (), }
こうもかける
let some_u8_value = Some(0u8); if let Some(3) = some_u8_value { println!("three"); }
7. モジュール
- モジュール - The Rust Programming Language → ここの説明は,古いので,今後 Rust 2018 Edition を使うなら,以下を参照したほうがいい:
8. 一般的なコレクション
8.1. ベクタ型
新しいベクタを生成する
let v: Vec<i32> = Vec::new(); let v = vec![1, 2, 3]; // 型推論が効く例
ベクタを更新する
let mut v = Vec::new(); // 型を書かなくていい例 v.push(5); // ←この行で型推論されている v.push(6); v.push(7); v.push(8);
ベクタの要素を読む
let v = vec![1, 2, 3, 4, 5]; let third: &i32 = &v[2]; let third: Option<&i32> = v.get(2);
戻ってくる型がちがう.get は失敗を前提にしている Option 型が返ってくる Some(x) or None
let v = vec![1, 2, 3, 4, 5]; let does_not_exist = &v[100]; // panic → 落ちる let does_not_exist = v.get(100); // None が返って来る // match で Some か None かを区別せざるを得ない→安全
所有権
let mut v = vec![1, 2, 3, 4, 5]; let first = &v[0]; // 不変借用が1個あるのに v.push(6); // v を可変借用できない → エラー
ベクタの値を走査する
let v = vec![100, 32, 57]; for i in &v { println!("{}", i); }
可変で借用
let mut v = vec![100, 32, 57]; for i in &mut v { *i += 50; }
Enumを使って複数の型を保持する
Rust の Vec は,全ての要素は,同じ型でなければならない. → C ならあたりまえだけど,Ruby や Python の人には信じられない. Rust には Enum がある!
たとえば,スプレッドシートのセルを表現した enum を Vector に収容:
enum SpreadsheetCell { Int(i32), Float(f64), Text(String), } let row = vec![ SpreadsheetCell::Int(3), SpreadsheetCell::Text(String::from("blue")), SpreadsheetCell::Float(10.12), ];
もしくは,トレイト (17章) と使えばいいよ.
8.2. 文字列型
文字列は,奥が深いので, 文字列型 - The Rust Programming Language よく読んで欲しい.
- 文字列とは
- UTF-8 の列
- Rust では,コアとして
&str
(別の場所に格納されたUTF-8エンコードされた文字列データへの参照).static な文字列リテラルもこれ. - 標準ライブラリとして
String
.伸張可能な Vec の一種 - その他,OsString,OsStr,CString,CStr など
新規文字列を生成する 空の文字列を生成:
let mut s = String::new();
to_string,String::from を使う例:
let data = "initial contents"; let s = data.to_string(); // the method also works on a literal directly: let s = "initial contents".to_string(); let s = String::from("initial contents"); // これも等価
文字列を更新する
let mut s = String::from("foo"); s.push_str("bar");
+演算子、またはformat!マクロで連結
let s1 = String::from("Hello, "); let s2 = String::from("world!"); let s3 = s1 + &s2; // s1はムーブされ、もう使用できないことに注意 // つまり strcat のようなことをしている
プラス演算子の実装(イメージです):
fn add(self, s: &str) -> String {
ここで疑問: add の第2引数は
&str
なのに,&String
を渡して大丈夫なの?理由:
add
呼び出しで&s2
を使える理由は、コンパイラが&String
引数を&str
に型強制(キャスト)してくれるためです add メソッド呼び出しの際, コンパイラは,参照外し型強制というものを使用し,ここでは,&s2
を&s2[..]
に変えるものと考えることができます.参照外し型強制について詳しくは, 第15章で議論します.addがs引数の所有権を奪わないので, この処理後もs2
が有効なString
になるわけです。複数の文字列を連結するのは面倒:
let s1 = String::from("tic"); let s2 = String::from("tac"); let s3 = String::from("toe"); let s = s1 + "-" + &s2 + "-" + &s3; // 面倒
こう書くといい
let s1 = String::from("tic"); let s2 = String::from("tac"); let s3 = String::from("toe"); let s = format!("{}-{}-{}", s1, s2, s3);
文字列に添え字アクセスする
これは,Rust ではコンパイルできない.
let s1 = String::from("hello"); let h = s1[0]; // コンパイルエラー
- Rust の String は UTF-8 エンコードされた文字の列としている.
- しかし,内部実装は,Vec<u8> になっていて,
s.len()
は,バイト数を返す (C と同じ挙動). (漢字は,UTF-8 で 1文字→3バイト)- Rust
- "乃村".len() → 6
- C
- strlen("乃村") → 6
- Ruby
- "乃村".length → 2
- Rust では
s[x]
で 1バイトだけを取り出すような操作を許したくない (取り出した1バイトは,もはや文字ではいから). - C だと,
s[x]
は,おかまいなしに漢字1文字(3バイトからなる)の途中の1バイトを取り出すことになるだろう. → つまりただのバイト列としかとらえていない. - Ruby は,
s[x]
とした場合,バイト指向ではなく,完全にUTF-8の有効な1文字を取り出せる.length もバイト数ではなく,文字数を返す. だが,文字数を返すコストはかなり大きい.なぜなら,UTF-8 は可変長なので,頭からお尻まで文字の区切を見付けて数え上げる必要があるから. この挙動は,ゼロコスト抽象化を標傍する Rust には受け入れられない.
文字列をスライスする
これは,Rust でも許しているが,容易に失敗する.
let hello = "Здравствуйте"; // キリル文字は1文字2バイト let s = &hello[0..4]; // 0..4 は4バイトであることに注意.つまり最初の2文字
文字境界でない所でスライスを取ると,落ちる.
let hello = "Здравствуйте"; // キリル文字は1文字2バイト let s = &hello[0..1]; // 落ちる
文字列を走査するメソッド群
文字列を文字毎に切り出して何かをしたい場合は,イテレータで操作するしかない.
for c in "नमस्ते".chars() { println!("{}", c); }
バイナリとみなして,バイト単位で取り出したいとき:
for b in "नमस्ते".bytes() { println!("{}", b); }
- 文字列はそう単純じゃない
- 文字列は,実は複雑
- Rust では,UTF-8 を強制することによって,複雑さを増しているように思える
- ASCII を仮定してしまって書いたコードのエラー事前に防いでいるともいえる
8.3. ハッシュマップ
新規ハッシュマップを生成する
use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.insert(String::from("Yellow"), 50);
2つの Vec を zip する方法
use std::collections::HashMap; let teams = vec![String::from("Blue"), String::from("Yellow")]; let initial_scores = vec![10, 50]; let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();
ハッシュマップと所有権
一旦挿入されたら、キーと値はハッシュマップに所有される.i32 とか Copy を実装している型はコピーされる.
use std::collections::HashMap; let field_name = String::from("Favorite color"); let field_value = String::from("Blue"); let mut map = HashMap::new(); map.insert(field_name, field_value); // field_nameとfield_valueはこの時点で無効になる.試しに使ってみて // どんなコンパイルエラーが出るか確認してみて!
参照を渡してもいいが,ライフタイム制約を守る必要がある (10章)
ハッシュマップの値にアクセスする
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"); let score = scores.get(&team_name); // → Some(&10) が返ってくる
全ての key-value を取得する
use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.insert(String::from("Yellow"), 50); for (key, value) in &scores { println!("{}: {}", key, value); }
- ハッシュマップを更新する
値を上書きする
use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.insert(String::from("Blue"), 25); println!("{:?}", scores); // → {"Blue": 25}
キーに値がなかった時のみ値を挿入する
use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.entry(String::from("Yellow")).or_insert(50); scores.entry(String::from("Blue")).or_insert(50); println!("{:?}", scores); // → {"Yellow": 50, "Blue": 10}
古い値に基づいて値を更新する
use std::collections::HashMap; let text = "hello world wonderful world"; let mut map = HashMap::new(); for word in text.split_whitespace() { let count = map.entry(word).or_insert(0); *count += 1; } println!("{:?}", map); // → {"world": 2, "hello": 1, "wonderful": 1}
or_insert
関数は,このキーに対する値への可変参照(&mut V)
を返す.