profile

Rust のBook見たのでメモ1

Published at

2025/01/09

Updated at

2025/01/09

Written by

marromugi

wasm の実装がしたかったので、Rust のドキュメントを超ざっと見た。
間違ってることも多いと思うけど、一応メモ程度に書いていきます。
読んだのは以下のドキュメントです。

1. 事始め

インストール方法とかまとめてくれている、参考になります。

2. 数当てゲームのプログラミング

試しにプログラムを作ってみる会

3. 一般的なプログラミングの概念

変数は標準で不変だよ。変えるなら mut をつけてね。
Rust は基本変更するなら宣言するパターンが多そう。
// NG
fn main() {
    let x = 5;
    println!("The value of x is: {}", x);
    x = 6; // ERROR: 不変変数`x`に2回代入できません
    println!("The value of x is: {}", x);
}

// OK
fn main() {
    let mut x = 5; // 可変であることを定義
    println!("The value of x is: {}", x);
    x = 6;
    println!("The value of x is: {}", x);
}
配列は一度定義したら固定長、変えたいときは Vector を使える。

式と文についての引用

とは、なんらかの動作をして値を返さない命令です。 は結果値に評価されます。ちょっと例を眺めてみましょう。
なのでこんな実装は NG。let は文なので何も返さないが、let x の右辺は式を求めているためエラーになる。
fn main() {
    let x = (let y = 6); // 式を予期しましたが、文が見つかりました (`let`)
}
戻り値がある場合はその変数や定数をポツンと置くだけ
fn plus_one(x: i32) -> i32 {
    x + 1
}

所有権を理解する

所有権の説明の前にスタックとヒープの解説がある。(一部抜粋)
サイズがわかっている時はスタック、わからない時はヒープを使うような感じ?
スタックもヒープも、実行時にコードが使用できるメモリの一部になりますが、異なる手段で構成されています。 データへのアクセス方法のおかげで、スタックは高速です: 新しいデータを置いたり、 データを取得する場所を探す必要が絶対にないわけです。
スタックは、得た順番に値を並べ、逆の順で値を取り除いていきます。スタックを高速にする特性は他にもあり、それはスタック上のデータは全て既知の固定サイズでなければならないということです。
コンパイル時にサイズがわからなかったり、サイズが可変のデータについては、代わりにヒープに格納することができます。
変数がスコープを抜けたら、Rustは自動的にdrop関数を呼び出し、 その変数が使っていたヒープメモリを片付ける
{                      // sは、ここでは有効ではない。まだ宣言されていない
    let s = "hello";   // sは、ここから有効になる

    // sで作業をする
}                      // このスコープは終わり。もうsは有効ではない。ドロップしているから
String について、こういう実装がある時
let s1 = String::from("hello");
let s2 = s1;
String の場合、Rust は s1 のポインタを s2 に渡す。
この時、s1 と s2 は同じポインタを見ていると思いきや、そうではないらしい。
なぜなら2重解放の危険性があるからです。
s2s1がスコープを抜けたら、 両方とも同じメモリを解放しようとします。これは二重解放エラーとして知られ、以前触れたメモリ安全性上のバグの一つになります。 メモリを2回解放することは、memory corruption (訳注: メモリの崩壊。意図せぬメモリの書き換え) につながり、 セキュリティ上の脆弱性を生む可能性があります。
そこで Rust では、最初の変数である s1 を無効化する。
これをs1s2ムーブされたと表現されるらしい。(所有権が移る)
ムーブせずに変数を渡したい場合は、参照を使えば良い。参照も宣言的で & を前置詞としてつけてあげる。
参照は所有権を持たないので、スコープを抜けてもドロップされない。
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.len()
}
関数の引数に参照を取ることを借用と呼ぶみたい。借用は借り物なので、基本的に値を変更したりはできない。
関数の引数に参照を取ることを借用と呼びます。現実生活のように、誰かが何かを所有していたら、 それを借りることができます。用が済んだら、返さなきゃいけないわけです。
しかし変更もできる。&mut をつけてあげれば、可変な参照として利用可能。
fn main() {
    let mut s = String::from("hello");

    change(&mut s);
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}
可変な参照は複数持てない。これは可変な参照が複数あると、同じタイミングで変更されたりとデータ競合が起こるため。
  • 2つ以上のポインタが同じデータに同時にアクセスする。
  • 少なくとも一つのポインタがデータに書き込みを行っている。
  • データへのアクセスを同期する機構が使用されていない。
また、不変な参照をしている間は、可変な参照をすることはできない。一方では不変を保証しているのに、他で変えられるのは確かに微妙です。
参照が浮くことは厳禁。ダングリング参照によって、実体がない参照が生まれることは望ましくないです。Rust はコンパイル時にこれを防いでくれます。
fn main() {
    let reference_to_nothing = dangle();
}

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

    &s
}
所有権のないデータ型にスライスがある。
所有権のない別のデータ型は、スライスです。スライスにより、コレクション全体ではなく、 その内の一連の要素を参照することができます。
例として以下のプログラムを、同サイトからお借りします
fn first_word(s: &String) -> usize {
    let bytes = s.as_bytes();

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

    s.len()
}

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

    let word = first_word(&s); // word will get the value 5
                               // wordの中身は、値5になる

    s.clear(); // this empties the String, making it equal to ""
               // Stringを空にする。つまり、""と等しくする

    // word still has the value 5 here, but there's no more string that
    // we could meaningfully use the value 5 with. word is now totally invalid!
    // wordはまだ値5を保持しているが、もうこの値を正しい意味で使用できる文字列は存在しない。
    // wordは今や完全に無効なのだ!
}
s が消えても、word が 5 という情報は持ち続けているので、事実上この 5 が何を意味しているのかわからないプログラムになってしまう。
そうすると、sword の2つの変数をプログラマ自身が管理し続けないといけない。。
これを楽にしてくれるのがスライス。
first_word をこんな感じに書いてみる。usize ではなく &str(スライス)を返してあげる。すると、Stringへの参照が有効なままであることをコンパイラが、 保証してくれる。
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[..]
}
この上で同様のプログラムを実行すると、以下のエラーになる。first_word に不変な参照として s を渡しているのに、clear で変更しようとしているからです。
(エラー: 不変として借用されているので、sを可変で借用できません)
疲れたので一旦ここまでにします、続きはまた今度書きます🥱