所有権とは
Rustはガベージコレクタなしでメモリ安全性を実現するため、所有権システムを採用しています。
所有権のルール
- 各値は所有者(変数)を持つ
- 所有者は同時に1つだけ
- 所有者がスコープを抜けると値は破棄される
fn main() {
let s1 = String::from("hello"); // s1が所有者
let s2 = s1; // 所有権がs2に移動(ムーブ)
// println!("{}", s1); // エラー!s1はもう使えない
println!("{}", s2); // OK
} // s2がスコープを抜け、メモリが解放される
ムーブとコピー
ムーブ(所有権の移動)
// ヒープに格納される型はムーブ
let s1 = String::from("hello");
let s2 = s1; // ムーブ
// s1は無効
// 関数に渡してもムーブ
fn take_ownership(s: String) {
println!("{}", s);
}
let s3 = String::from("world");
take_ownership(s3);
// s3は無効
コピー(値のコピー)
// スタックに格納される型はコピー
let x = 5;
let y = x; // コピー
println!("{}, {}", x, y); // 両方使える
// Copy トレイトを実装している型
// - 整数型、浮動小数点型、bool、char
// - これらだけを含むタプル
クローン(明示的なコピー)
let s1 = String::from("hello");
let s2 = s1.clone(); // 明示的にコピー
println!("{}, {}", s1, s2); // 両方使える
借用(参照)
所有権を移動せずに値を使う方法です。
不変参照
fn main() {
let s = String::from("hello");
let len = calculate_length(&s); // 借用
println!("{}の長さは{}", s, len); // sはまだ使える
}
fn calculate_length(s: &String) -> usize {
s.len()
}
// 複数の不変参照はOK
let s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{}, {}", r1, r2);
可変参照
fn main() {
let mut s = String::from("hello");
change(&mut s); // 可変借用
println!("{}", s); // hello, world
}
fn change(s: &mut String) {
s.push_str(", world");
}
// 可変参照は同時に1つだけ
let mut s = String::from("hello");
let r1 = &mut s;
// let r2 = &mut s; // エラー!
println!("{}", r1);
参照のルール
// ルール1: 不変参照は複数OK
let s = String::from("hello");
let r1 = &s;
let r2 = &s;
// ルール2: 可変参照は1つだけ
let mut s2 = String::from("hello");
let r3 = &mut s2;
// ルール3: 不変と可変は同時に持てない
let mut s3 = String::from("hello");
let r4 = &s3;
// let r5 = &mut s3; // エラー!
println!("{}", r4);
ダングリング参照の防止
// コンパイルエラー!
fn dangle() -> &String {
let s = String::from("hello");
&s // sはこの関数の終わりで解放される
}
// 正しい方法:所有権を返す
fn no_dangle() -> String {
let s = String::from("hello");
s // 所有権を移動
}
スライス
let s = String::from("hello world");
// 文字列スライス
let hello = &s[0..5];
let world = &s[6..11];
// 省略形
let hello2 = &s[..5];
let world2 = &s[6..];
let whole = &s[..];
// 文字列リテラルはスライス
let s2: &str = "hello";
// 配列スライス
let arr = [1, 2, 3, 4, 5];
let slice: &[i32] = &arr[1..3]; // [2, 3]
ライフタイム
参照が有効な期間を示します。
// ライフタイム注釈
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let s1 = String::from("long string");
let s2 = String::from("short");
let result = longest(&s1, &s2);
println!("{}", result);
}
構造体のライフタイム
struct ImportantExcerpt<'a> {
part: &'a str,
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().unwrap();
let excerpt = ImportantExcerpt {
part: first_sentence,
};
println!("{}", excerpt.part);
}
静的ライフタイム
// プログラム全体で有効
let s: &'static str = "I have a static lifetime.";
実践例
struct User {
name: String,
age: u32,
}
impl User {
// 所有権を取る
fn new(name: String, age: u32) -> User {
User { name, age }
}
// 不変参照
fn name(&self) -> &str {
&self.name
}
// 可変参照
fn set_age(&mut self, age: u32) {
self.age = age;
}
// 所有権を消費
fn into_name(self) -> String {
self.name
}
}
fn main() {
let mut user = User::new(String::from("太郎"), 25);
println!("{}", user.name());
user.set_age(26);
let name = user.into_name();
// userはもう使えない
}
まとめ
- 各値は1つの所有者を持つ
- 所有権は移動(ムーブ)される
- 参照(
&)で借用、値は移動しない - 可変参照(
&mut)は同時に1つだけ - ライフタイムで参照の有効期間を保証
- コンパイラがメモリ安全性を保証
次回はエラー処理について学びます。