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は
                                       // もう有効ではない
    // → メモリを返さなきゃ!
    
  • 変数とデータの相互作用: ムーブ

    代入ってコピーだよね?

    let x = 5;
    let y = x; // これはコピー ∵ コピーのコストが低いから
    
    • メモリ上での大きさが固定で単純なコピーで済む値 → コピーされる
    • Rust では,スタック上に置ける固定長の値は,
      • 代入によってコピーされる
      • スコープを抜けると自動で drop される
    • これは,他の言語でも同じ.Java でいう,primitive 型のイメージ.

    では,可変長の文字列 String は? (可変な長さを持つ → スタックには積めない)

    let s1 = String::from("hello");
    

    下のようになる.左は スタック,右は ヒープ 上に存在する.

    Sorry, your browser does not support SVG.

    図1: s1に束縛された"hello"という値を保持するStringのメモリ上の表現

    これを s2 に代入しても以下のようにはならない (Deep Copy しない)

    let s1 = String::from("hello");
    let s2 = s1; // コピーなのか?
    

    Sorry, your browser does not support SVG.

    図2: Rust がもしヒープデータもコピーするとした場合の s2 = s1 の結果

    C脳なら,s2 に代入するとこんなイメージ? (Shallow Copy)

    Sorry, your browser does not support SVG.

    図3: s1のポインタ、長さ、許容量のコピーを保持する変数s2のメモリ上での表現

    しかし,これは問題:

    • 2個所から文字列の本体(ヒープ)にある → メモリをいつ解法すればいい?
    • s1 と s2 のどちらがスコープを抜けたときに drop すればいい?
    • 関数の引数に渡す (一種のコピー) したらどうなるの?

    → Rust では,代入によって,s1 を無効化することで解決: 所有権の移動 (move) という

    let s1 = String::from("hello");
    let s2 = s1; // 所有権が s2 に move.s1 は以降参照できない
    

    Sorry, your browser does not support SVG.

    図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);
      

      この場合は,こうなる

      Sorry, your browser does not support SVG.

      図5: Rust がもしヒープデータもコピーするとした場合の s2 = s1 の結果

  • スタックのみのデータ: コピー
    • スタックの中だけに収まるデータは,「Copy トレイトを実装している」 (Copy 可能なオブジェクトのクラスみたいなイメージ)
      • あらゆる整数型.u32など.
      • 論理値型,bool,true/false という値がある.
      • あらゆる浮動小数点型,f64など.
      • 文字型、char.
      • タプル.ただ Copy の型だけを含む場合.例えば,(i32, i32) は,Copyだが,(i32, String) は,違う.
  • 戻り値とスコープ
    • 関数呼出しで渡したり返したりしても所有権が移動する

      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はスコープ外になる。けど、参照しているものの所有権を持っているわけではないので
      // 何も起こらない
    

    こんなイメージ

    Sorry, your browser does not support SVG.

    図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];
    

    メモリ中で起こっていること:

    Sorry, your browser does not support SVG.

    図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. モジュール

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) を返す.

著者: 乃村能成