なぜ所有権が必要なのか
多くの言語では、メモリの管理をガベージコレクタ(自動でメモリを解放する仕組み)やプログラマ自身の手作業に頼っています。Rustは、コンパイル時に適用される「所有権」というルールによって、プログラムを実行する前にメモリ関連の不具合の多くを検出します。
所有権の3つのルール
- Rustの値は、それぞれ**所有者(owner)**と呼ばれる変数を持つ。
- 値の所有者は、同時に1つだけである。
- 所有者がスコープ(
{ }の範囲)から抜けると、値は破棄される。
これだけではイメージしづらいので、具体的なコードで見ていきましょう。
ムーブ(move)
次のコードは、String型(ヒープ上にデータを持つ、可変長の文字列型)を使った例です。
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1の所有権がs2にムーブ(移動)する
println!("{}", s2); // OK
// println!("{}", s1); // エラー:s1はもう有効ではない
}
let s2 = s1;という行では、文字列の中身がコピーされるのではなく、**所有権そのものがs1からs2に移動(ムーブ)**します。この結果、s1は無効になり、以降s1を使おうとするとコンパイルエラーになります。
Copy型:スカラ値はムーブされない
一方で、i32やboolのような単純な値(スタック上に収まるサイズの固定された値)はCopyという特性を持っており、代入時にムーブではなくコピーが行われます。
fn main() {
let x = 5;
let y = x; // xの値がyにコピーされる
println!("x = {}, y = {}", x, y); // どちらも使える
}
clone:明示的に複製する
どうしても複製が必要な場合は、.clone()を使って明示的にデータを複製できます。
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2); // どちらも使える
}
関数呼び出しと所有権
関数に値を渡すときも、代入と同じ規則が適用されます。
fn takes_ownership(s: String) {
println!("受け取った: {}", s);
} // ここでsはスコープを抜け、値が破棄される
fn main() {
let s = String::from("hello");
takes_ownership(s); // sの所有権が関数に移動する
// println!("{}", s); // エラー:sはもう有効ではない
}
関数を呼び出すたびに所有権が移動してしまうと不便な場面が多くあります。この問題を解決するのが、次の章で学ぶ「参照と借用」です。
演習:文字列の長さを求める
コード提出フォーム
RUST安全なサーバー側採点連携が未接続のため、現在は提出できません。
採点基盤準備中
公開テストケース
読み込み中…