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重解放の危険性があるからです。
s2とs1がスコープを抜けたら、 両方とも同じメモリを解放しようとします。これは二重解放エラーとして知られ、以前触れたメモリ安全性上のバグの一つになります。 メモリを2回解放することは、memory corruption (訳注: メモリの崩壊。意図せぬメモリの書き換え) につながり、 セキュリティ上の脆弱性を生む可能性があります。
そこで Rust では、最初の変数である s1 を無効化する。
これを
s1はs2にムーブされたと表現されるらしい。(所有権が移る)ムーブせずに変数を渡したい場合は、参照を使えば良い。参照も宣言的で
& を前置詞としてつけてあげる。参照は所有権を持たないので、スコープを抜けてもドロップされない。
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 が何を意味しているのかわからないプログラムになってしまう。そうすると、
s とword の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を可変で借用できません)
疲れたので一旦ここまでにします、続きはまた今度書きます🥱
