Rust Book 読書メモ

Jan 9, 2022 ( Feb 11, 2022 更新 )

https://doc.rust-lang.org/book/

感想

  • 3-2 まで
    • shadowing どういうときに便利なのかわかっていない
      • 普通に宣言すればいいのでは?
    • release オプションつけてるときに overflow を wrapping される件、デフォルトが panic で困るときに wrap method つけるほうがいいと思うんだけどシステムプログラマ的にはこっちが便利?
      • overflow の wrapping で実際とは異なるカウントアップになった場合に業務?的に困る場合もある気がするが、どうなんだろう
  • 3-5 まで
    • expressions は値を返す, statement は値を返さない
      • return break continue は値を返す statement って…コト!?
  • 4-1 まで
    • return しなかったらスコープから外れて値が使えなくなる、まあ参照渡しじゃないから、、、みたいなことを考えたらそうだねという気もする
    • move の挙動は忘れたら、え?てなりそうだけど、再代入って普通しないから気になくてよさそう、、、
  • 4-2 まで
    • 感想とくにないかも、、
    • しいていうなら &foo mut &foo の違いか。 let mut foo であれば &foo // immutable reference&mut foo // mutable reference の違いがあるが、当然 let foo&foo しかできず、 &mut foo のパターンはありえない、まあそれはそうって感じ
  • 4-3 まで
    • Slice というか mutable な値の borrowing を使って、immutable reference として返すやり方は安全でよいなと思った
  • 5-1 まで
    • やっぱり Rust の ownership 難しい、という印象
    • とはいえ、GC無しで安全に使えて… ということを考えるとこういうもんなのかな?GCのある言語ばかり使ってきた身としては難しい…
    • immutable borrowing + lifetime のところを読んでみないとまだなんとも言えないか…

1 章

rustfmt について

rustfmt というフォーマット用のツールがある。 rustup component add rustfmt で導入できるとのこと。 実行すると、

info: component ‘rustfmt’ for target ‘x86_64-apple-darwin’ is up to date

と言われるので最初から入ってるぽかった…。

これは Cargo プロジェクトのフォーマットをするので、Cargo の設定ファイルが必要。

cargo

https://doc.rust-lang.org/cargo/

cargo new hello_cargo --bin

  • cargo new で新しい Cargo プロジェクトを作成する
  • cargo build でビルド、main を実行するだけなら cargo run、コンパイルできるか検証するには cargo check(build よりも速い)
  • cargo build --release で実行ファイルの最適化が効くが、コンパイルが遅い。ベンチマークを取りたいときは –release オプションをつけなさいとのこと

2 章

prelude に載っているのが use しなくても使える型。

変数宣言

let で変数宣言する。 Rust では let で宣言する変数は不変なので、 mutable な変数は mut をつける。

Associated Function

type に実装されている function のことを Associated Function と呼ぶ。 String::new(); みたいなやつ。

参照

&

Enum

Result は Enum の一種

crate

git clone で ssh と認証を使いたい場合は CLI を使って依存関係解決することもできる。

https://doc.rust-lang.org/cargo/reference/config.html#netgit-fetch-with-cli

[net]
git-fetch-with-cli = true

cargo update すると依存関係も最新化される。

arms

  • match で始まって pattern にマッチするか
let guess: u32 = match guess.trim().parse() {
    Ok(num) => num,
    Err(_) => continue,
};

3 章

  • immutable にすると不変なのでわかりやすいけど、でかい structures を扱うときに mutable にするとパフォーマンスが良いということはある

  • const は immutable だが、 計算後の値ではなく runtime の起動時にセットされるようなもの。Rust はコンパイル時に const を計算する

  • shadowing は自分的にはしんどい、普通に spaces_str で認知負荷的にも困らない

  • scalar と compound 型がある

    • scalar はひとつの値を意味する: integers, floating-point numbers, Booleans, and characters
    • compound は複数の値を持つもの
  • Rust は勝手に型推論するが、複数の型が帰るケースでは書き手が annotation をつけて決定する

let guess: u32 = "42".parse().expect("Not a number!");
  • isize usize に関してはアーキテクチャにより 32 か 64 か変わる

  • number literals がある。 e.g. 57u8, 98_222

  • scalar 型の overflow について、release タグをつけた場合には panic しない。release の場合は、overflow したら max 値まで wrap して処理を継続する

    • saturating_*, overflowing_*, checked_*, wrapping_* とか制御するメソッドがあるので、 handle したい場合はこれらを使う
  • float はデフォルトで f64

  • 計算用オペレータ各種

  • char は single quotes で表現、string は double quotes で表現

  • tuple は fixed length でサイズを変えられない型

  • pattern matching で tuple から値を取り出すこともできる

    • destructing
  • あるいは .{num} でアクセスすることができる

fn main() {
    let tup = (500, 6.4, 1);

    let (x, y, z) = tup;

    println!("The value of y is: {}", y);
}
  • 値がなにもない tuples は () で表現され、unit type とも言う(値は unit value)。expression を使ったときに、対象に何もない場合は暗黙的に unit type が返る

  • array は tuples と違って単一の型のみ保持するが、tuples と同じところは fixed size であるところ

    • vector type というサイズ可変のコレクション型もある
    • データを heap ではなく、stackに置きたいときに便利
let a: [i32; 5] = [1, 2, 3, 4, 5];
let a = [3; 5]; // let a = [3, 3, 3, 3, 3];
  • 不正な index range アクセスは panic で終了
  • array は value の更新不可
fn main() {
    let list: [u64; 5] = [1; 5];
    println!("list: {}", list[1]);
    list[1] = 100; // error
}
  • 細かい話だが function に与える concrete value を arguments と言い、function definition の方は parameters というらしい。。。
  • コード中でどの型を意図するか指定する必要をなくすため、function の parameters は type 必須にしているらしい
  • Rust は expression based language
    • statement は値を返さない
      • let は statement なので、let x = (let y = 6); はできない
      • C とか Ruby だと assignment は値を返すので x = y = 6 みたいな書き方もできる
    • expressions は値を返すために evaluate する
      • statement の中に expressions を含めることはできる
      • macro を call するのは expressions
      • {} block は expression なので以下のようなコードは OK
fn main() {
    let y = {
        let x = 3;
        x + 1
    };

    println!("The value of y is: {}", y);
}
  • expressions の場合はセミコロンで終わらせない。セミコロンで終わらせるのは statement になる
  • function の最後の expressions の戻り値は return value となる / Rust では return は省略するケースが多いっぽい?
fn main() {
    let x = plus_one(5);

    println!("The value of x is: {}", x);
}

fn plus_one(x: i32) -> i32 {
    x + 1;
    // Followings work:
    // return x + 1;
    // x + 1
}
  • if expressions は arms とも呼ばれる(前やったパターンマッチで使ったやつ)

    • というか if は expressions なので値の代入に使える
    • if condition は bool でなければいけない
      • operator != とかを使ってください
  • branches が複数あるときは match を使う

  • if expressions で let statement で返すときは type が決まらないので error になる場合がある。branch で返す値の型は同一でないとコンパイルできない

  • 繰り返し処理は loop while for

  • そして loop は expression

  • loop は明示的に終了することを記述しなければずっと繰り返す

    • break で終了
      • break continue は statement
    • continue で繰り返し処理の残りの部分を無視して最初から
  • 繰り返し処理は label 指定でどのループの処理に対する break continue か指示することができる

    • 通常は inner loop に対する指定となる
fn main() {
    let mut count = 0;
    'counting_up: loop {
        println!("count = {}", count);
        let mut remaining = 10;

        loop {
            println!("remaining = {}", remaining);
            if remaining == 9 {
                break;
            }
            if count == 2 {
                break 'counting_up;
            }
            remaining -= 1;
        }

        count += 1;
    }
    println!("End count = {}", count);
}
  • loop expression 内で値を返すために ; つけて statement として終了する感じ
  • while は condition つける
  • for element in list { // ...
fn main() {
    // (1..4) generates a Range standard type.
    for number in (1..4).rev() {
        println!("{}!", number);
    }
    println!("LIFTOFF!!!");
}

4 章

  • memory の管理方法

    • garbage collection
    • 自分で allocate and free
    • Rust はいくつかのルールセットをもとにオーナーシップで管理される。オーナーシップの機能でプログラム実行時に遅くなることはない(多分 garbage collection との対比)
  • システムプログラミングにおける stack and heap

    • stack は size が決められている
    • heap は size が可変。heap を使うときは一定の size メモリを確保することになる。memory allocator が一定の size の space を確保して pointer として返す(allocating)。
    • stack は heap より速い
    • heap space が大きいと allocation はコストが高くなる
    • function は stack に入れられる(パラメータとしての pointer は heap かもしれない)
    • 何が heap に入れられ、何が stack に入れられるか、 heap の重複データを避けて使っていないデータを削除することができれば space を使い切ることはない。 ownership がそれについて言及する
  • Rust のすべての値は owner をもっている

  • ある場面では owner は単一である

  • owner がスコープ外になると値は捨てられる

  • 通常 data type は size を持っており、stack から取り出されて独立したコピーとして使われる。別のスコープのコードで必要になったら再利用される

let s = String::from("hello");
  • string literals は不変
  • String は size が不定で heap を使う
  • literals はコンパイル時に埋め込まれるので速い
  • String の場合は runtime 時にメモリをリクエストしている
    • String の利用を完了したときに allocator にメモリを返してもらう必要がある
      • GC がある場合は GC が使わなくなったメモリを開放する
      • Rust の場合は out of scope になったら free とする
        • {} curly brackets の終了で drop(メモリ開放)する
    {
        let s = String::from("hello"); // s is valid from this point forward

        // do stuff with s
    }                                  // this scope is now over, and s is no
                                       // longer valid

以下の場合は 5 が stack に入れられる:

let x = 5;
let y = x;

String の場合は s1 の "hello" に対する pointer を s2 が持つことになる:

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

この場合、 s1 と s2 を drop しようとするとエラーになってしまう。これを double free error (2回メモリをfreeしようとしてエラー)という。 なので、 Rust の場合は上記のように pointer を代入するときは drop が発生する。以下のコードはコンパイルできない:

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

println!("{}, world!", s1);
  • shallow copy
    • capacity, length などはコピーするが値自体はコピーしない
  • Rust は shallow copy ではなく move をする / 最初の参照を無効にして、後の参照のみ有効にする
  • Rust は deeply copy しない。そのため runtime performance を損なわない
  • deeply copy したい場合は .clone() method を使う

以下の場合は heap のデータがコピーされている:

let s1 = String::from("hello");
let s2 = s1.clone();

println!("s1 = {}, s2 = {}", s1, s2);

以下の場合はコンパイルでき、動作するが、先程の例の move の挙動とは異なっている:

let x = 5;
let y = x;

println!("x = {}, y = {}", x, y);
  • integers などの stack に保持される値はそのままにされる(compile 時にサイズ計算され、stack にに保持されるので、shallow とか deep とか気にする必要がない)
  • Copy Drop trait
    • integer のような stack に保持する値については Copy trait を使うことができる
    • Copy trait を実装している場合は古い値も参照できる
    • Drop trait を実装していた場合は、Copy trait を実装できない。詳細は Derivable Traits参照

Here are some of the types that implement Copy:

  • All the integer types, such as u32.
  • The Boolean type, bool, with values true and false.
  • All the floating point types, such as f64.
  • The character type, char.
  • Tuples, if they only contain types that also implement Copy. For example, (i32, i32) implements Copy, but (i32, String) does not.

以下の場合、xはスコープに戻ってきてないので drop となる:

fn main() {
    let s = String::from("hello");  // s comes into scope

    takes_ownership(s);             // s's value moves into the function...
                                    // ... and so is no longer valid here

    let x = 5;                      // x comes into scope

    makes_copy(x);                  // x would move into the function,
                                    // but i32 is Copy, so it's okay to still
                                    // use x afterward

} // Here, x goes out of scope, then s. But because s's value was moved, nothing
  // special happens.

fn takes_ownership(some_string: String) { // some_string comes into scope
    println!("{}", some_string);
} // Here, some_string goes out of scope and `drop` is called. The backing
  // memory is freed.

fn makes_copy(some_integer: i32) { // some_integer comes into scope
    println!("{}", some_integer);
} // Here, some_integer goes out of scope. Nothing special happens.

以下のように return されてスコープに戻ってきている場合は move, したのちに drop となる:

fn main() {
    let s1 = gives_ownership();         // gives_ownership moves its return
                                        // value into s1

    let s2 = String::from("hello");     // s2 comes into scope

    let s3 = takes_and_gives_back(s2);  // s2 is moved into
                                        // takes_and_gives_back, which also
                                        // moves its return value into s3
} // Here, s3 goes out of scope and is dropped. s2 was moved, so nothing
  // happens. s1 goes out of scope and is dropped.

fn gives_ownership() -> String {             // gives_ownership will move its
                                             // return value into the function
                                             // that calls it

    let some_string = String::from("yours"); // some_string comes into scope

    some_string                              // some_string is returned and
                                             // moves out to the calling
                                             // function
}

// This function takes a String and returns one
fn takes_and_gives_back(a_string: String) -> String { // a_string comes into
                                                      // scope

    a_string  // a_string is returned and moves out to the calling function
}

次、一応 return 使わないで ownership 管理する reference の紹介らしい。。。

References and borrowings

  • pointer と reference の違いは、必ず valid な値であるということ
  • dereference をするには * を使う
  • reference の場合には ownership が function に移らないので、block の終わりに ownership が無効になるということがない
  • reference をつくることを borrowing という
  • reference の場合も、immutable な値に対する mutate 操作はできない
  • ある mutable な値を複数回 borrowing することはできない
    • immutable は 複数回 borrowing できる
    • これはコンパイル時に data racing を検知できるようにするため。以下のような場合が考えられる:
      • 2つ以上の pointer が同じデータに同時にアクセスしている
      • 少なくとも1つの pointer がデータに書き込みをしようとしている
      • データへのアクセスを同期にする仕組みがない
  • immutable な borrowing は複数回できるが、mutable borrowing は immutable 含めて複数回できない
    • immutable な borrowing は read だけなので他の操作に影響しないから、という理由とのこと
fn main() {
    let mut s = String::from("hello");

    let r1 = &s; // no problem as immutable borrowing
    let r2 = &s; // no problem as immutable borrowing
    let r3 = &mut s; // BIG PROBLEM as mutable borrowing

    println!("{}, {}, and {}", r1, r2, r3);
}
  • let mut foo は let したブロックで mutable なだけであって、 bar(&foo) というのは、 bar に reference を渡すだけなので、 bar のブロックの中では mutable というわけではない。bar(&mut foo) という使い方をすると mutable ということになる

以下のコードは r1 と r2 が使われないのでいいらしい。そうなの、、、

fn main() {
    let mut s = String::from("hello");

    let r1 = &s; // no problem
    let r2 = &s; // no problem
    println!("{} and {}", r1, r2);
    // variables r1 and r2 will not be used after this point

    let r3 = &mut s; // no problem
    println!("{}", r3);
}

  • ある pointer が保存されているメモリが開放されることによって、他者がと参照先がかち合ってしまうことを dangling pointer と呼ぶ
  • dangling pointer の例は以下。Rust だと immutable な s は dangle()` のブロック外で dropped されてしまうので、 dangleing pointer となってしまう。なのでコンパイラエラーとなる
fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String { // dangle returns a reference to a String

    let s = String::from("hello"); // s is a new String

    &s // we return a reference to the String, s
} // Here, s goes out of scope, and is dropped. Its memory goes away.
  // Danger!

  • 以下の例だと return された s の ownership が string に移るので、値は drop されない。そのため dangling pointer には当たらず OK
fn main() {
    let string = no_dangle();
}

fn no_dangle() -> String {
    let s = String::from("hello");

    s
}

Slices

  • .. range index
  • String Slices は有効なUTF-8文字の境界で使わなければエラーとなる。UTF-8文字のハンドリングについては Chapter8 で取り扱う
#![allow(unused)]
fn main() {
  let s = String::from("hello");

  let slice = &s[0..2];
  let slice = &s[..2];
}
#![allow(unused)]
fn main() {
  let s = String::from("hello");

  let len = s.len();

  let slice = &s[3..len];
  let slice = &s[3..];
}
#![allow(unused)]
fn main() {
  let s = String::from("hello");

  let len = s.len();

  let slice = &s[0..len];
  let slice = &s[..];
}
  • mutable な値を borrowing して immutable として代入した後に mutable な値を操作しようとするとコンパイルエラーとなる
fn first_word(s: &String) -> &str { // &str is immutable reference
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}

fn main() {
    let mut s = String::from("hello world");

    let word = first_word(&s);

    s.clear(); // error!

    println!("the first word is: {}", word);
}
  • deref coercion; dereference の強制
  • &str は Slice でもある

5章

Struct の定義と instance 化

#[derive(Debug)]

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn main() {
    let user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };
    let user2 = User {
        email: String::from("another@example.com"),
        ..user1
    };

    println!("user1: {:?}", user1);
    println!("user2: {:?}", user2);
}

これの結果は以下になる。 user1 の String field の owner が user2 に移っているので user1 の String field が参照できない。 難しすぎるやろw

error[E0382]: borrow of partially moved value: `user1`
  --> src/main.rs:22:29
   |
17 |       let user2 = User {
   |  _________________-
18 | |         email: String::from("another@example.com"),
19 | |         ..user1
20 | |     };
   | |_____- value partially moved here
21 |
22 |       println!("user1: {:?}", user1);
   |                               ^^^^^ value borrowed here after partial move
   |
   = note: partial move occurs because `user1.username` has type `String`, which does not implement the `Copy` trait

For more information about this error, try `rustc --explain E0382`.
error: could not compile `structs` due to previous error

ちなみに以下のように active の場合はアクセスできる。それは、copy trait を実装しているからとのこと。

    println!("user1: {:?}", user1.active);
user1: true
user2: User { active: true, username: "someusername123", email: "another@example.com", sign_in_count: 1 }

上記の例では String を使っており、 copy trait がないため ownership が移っており使えないよ、という例示をしているが、じゃあ reference を使って immutable borrowing をしたらどうか、という場合は lifetime を指定していないよというエラーになる。 lifetime については10章で説明されるらしい。

struct User {
    username: &str, // Missing lifetime specifier
    email: &str, // Missing lifetime specifier
    sign_in_count: u64,
    active: bool,
}

fn main() {
    let user1 = User {
        email: "someone@example.com",
        username: "someusername123",
        active: true,
        sign_in_count: 1,
    };
}
Return to top