Rustの概要
- 目的 : 高速・安全・並行処理に強いプログラム言語
- 特徴
- メモリ安全、ガーベージコレクタなしで安全性を保証(コンパイル時チェック)
- ゼロコスト抽象化 : 高レベルの構文でも低レベルな処理速度
- 所有権システム : ポインタの安全性やランタイムをコンパイル時に管理
- マルチスレッド安全性 : デーテ競合をコンパイル時に防ぐ
Rustの考え方
Rustの根本的な考え方は「安全性と効率は両立できる」というもの。
そのために以下の仕組みがある
- 所有権
- 変数には「所有権」が一つだけ存在
- 所有者がスコープを抜けると、そのデータは自動で破棄される
- 借用
- 参照(&)で一時的に借りることができる
- 読み取り専用は複数OK、書き込みは一つだけ
- ライフタイム
- 参照が無効になるタイミングをコンパイル時に保証
これらにより、Cなどでありがちなメモリ破壊やダングリングポインタを防ぐ。
基本的な使い方
rustは下記コマンドを使用してインストールを実施する
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
インストール後、次のコマンドでシェルにPATHを読み込ませる
. "$HOME/.cargo/env"
# プロジェクト作成
cargo new hello_rust
reating binary (application) `hello_rust` package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
cd hello_rust
# 実行
cargo run
Compiling hello_rust v0.1.0 (/home/yusuke/project/rust/hello_rust)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.16s
Running `target/debug/hello_rust`
Hello, world!
変数の扱い方
-
基本構文
let x = 5;
let x: i32 = 5; // 明示的に型指定
- let で変数宣言
- デフォルトでは不変
- 型はコンパイラが推論してくれる(必要なら明示できる)
- 型を間違えてもコンパイル時に検出できる
- パフォーマンス最適化(適切なビット幅を選べる)
- API や関数で意図を明確にできる
型名 ビット幅 符号 範囲(例) i8 8 符号付き -128 ~ 127 u8 8 符号なし 0 ~ 255 i32 32 符号付き 約 -21億 ~ +21億 u32 32 符号なし 0 ~ 約42億 i64 64 符号付き もっと広い u64 64 符号なし もっと広い -
可変変数
デフォルトでは不変なので、値を変えたい場合は、mutを付けるlet mut count = 0; count = 10; // OK
-
不変と可変の違い
- 不変: 値を再代入できない(コンパイルエラーになる)
- 可変: 再代入できるが、所有権や借用ルールは守る必要がある
-
不変と可変の違い
-
変数のシャドーイング
- let 変数名 = 値 を同じ名前で書き直すと古い変数は破棄され、新しい変数が作られる
- 変数の「再代入」ではなく「再宣言」
-
特徴
- mut なしで値を変更できる(実際は新しい変数)
- 型の変更も可能
- 古い変数は完全に消えるため、参照や値の衝突が起きない
- 安全性を損なわない理由
- 古い変数は「死んで」おり、生きたままの変更ではないため、Rust の不変ルールと矛盾しない
mut(可変) シャドーイング 実態 同じ変数の中身を変更する 別の新しい変数を作る 型の変更 不可 可能 安全性への影響 参照がある場合は制約が厳しい 古い変数は破棄されるので安全 let x = 5; // 不変 let x = x + 1; // 新しい変数で上書き(再宣言) println!("{}", x); // 6
-
定数
const PI: f64 = 3.14159;
-
const キーワードで宣言
-
型指定(: f64 の部分)が必須
-
値はコンパイル時に決まっている必要がある (絶対に不変)
-
実行中はもちろん、シャドーイングや再代入も不可能
const TAX: f64 = 0.1; fn main() { const TAX: f64 = 0.08; // ❌ エラー println!("{}", TAX); }
-
-
命名規則としては大文字スネークケース(MAX_POINTS など)が慣例
-
所有権
-
所有権の概要
Rust ではすべての値には「所有者(owner)」がただ1つだけ存在し、
その所有者が「アクセス権」と「メモリ管理の責任」を持つ。 -
所有権のルール
- すべての値には所有者が1人だけ
- 所有者がスコープを抜けたら値は破棄され、メモリも自動解放
- 所有権は移動(ムーブ)できる
-
所有権が管理しているもの
- アクセス権限:その変数の読み書きができる唯一の権利
- メモリ領域の責任:スコープ終了時に安全に解放する責任
-
所有権の動き
- ムーブ(Move)
fn print_string(s: String) { println!("{}", s); } fn main() { let s1 = String::from("hello"); print_string(s1); // 所有権が関数引数にムーブ println!("{}", s1); // ❌ s1はもう使えない }
- コピー
let x = 5; let y = x; // 値がコピーされる(整数型などスタックだけの型) println!("{}", x); // OK
- クローン
let s1 = String::from("hello"); let s2 = s1.clone(); // ヒープの中身も複製 println!("{}, {}", s1, s2); // OK
-
所有権のメリット
- メモリ安全
- 二重解放なし
- ダングリングポインタなし
- ガーベジコレクタ不要
- 実行時コストゼロ
- 解放タイミングが予測可能
- データ競合防止
- マルチスレッドでも安全
- メモリ安全
-
まとめ
- 所有権は「変数への唯一のアクセス権+そのメモリの片付け責任」。
- スコープ終了と同時に権限も責任も消え、メモリは自動で解放される。
借用
- 借用の概要
- 所有権は移動させずに、変数を一時的に使用されてもらうこと
- 所有者は変わらないので、借用が終わったら元の変数をそのまま使える
- & を付けて参照を作る
2. 不変借用
-
特徴
- 複数同時に借用できる(読み専用なので安全)
- 借用先では読み込みはできるが書き込みはできない
fn main() { let s = String::from("hello"); print_string(&s); // sを不変で借用 println!("{}", s); // 借用後もsは使える } fn print_string(s: &String) { println!("{}", s); }
3. 可変借用
-
特徴
- 1つのデータに対して同時に1つだけ可変借用が可能
- 可変借用中は不変借用も禁止(データ競合防止)
fn main() { let mut s = String::from("hello"); change_string(&mut s); // 可変で借用 println!("{}", s); // 借用後もsは使える } fn change_string(s: &mut String) { s.push_str(", world"); }
4. 借用のメリット
- 所有権を移動させないので、元の変数を借用後も使える
- 安全にデータを共有できる(コンパイル時にデータ競合チェック)
- 関数間のデータ受け渡しが効率的(コピー不要)
5. 所有権と借用の違い
所有権 | 借用 |
---|---|
データの責任ごと渡す | アクセス権だけ渡す |
ムーブ後は元の変数は使えない | 借用後も元の変数を使える |
解放の責任も移動 | 解放の責任は元の所有者のまま |
ライフタイム
-
ライフタイムの概念
- 参照が有効でいられる期間(寿命) を表す概念。
-
なぜ必要か
fn main() { let r; { let x = 5; r = &x; // ❌ xはこのブロックを抜けると消える } println!("{}", r); // 参照切れ(Cなら未定義動作) }
Rustではコンパイルエラーになる:
error[E0597]: `x` does not live long enough
理由:x のライフタイムが r より短いので、r は無効なメモリを指すことになる。
-
ライフタイムの基本ルール
- 借用は元の変数が有効な間だけ生きられる
- 元の変数がスコープを抜けたら、その参照も同時に死ぬ
- コンパイラは「すべての参照が有効なメモリを指している」ことを保証する
-
まとめ
- ライフタイムは参照の有効期間
- 所有者より長生きする参照は作れない
- コンパイル時に参照切れを完全に防ぐ