rust-ownership - 解説
実装の詳細
所有権の移動パターン
// パターン1: 関数呼び出しで移動
fn take_ownership(s: String) {
println!("{}", s);
} // s は drop される
let s = String::from("hello");
take_ownership(s);
// s は使用不可
// パターン2: 所有権を返す
fn give_ownership() -> String {
String::from("hello")
}
let s = give_ownership();
// s が所有権を持つ
// パターン3: 受け取って返す
fn take_and_give_back(s: String) -> String {
s // 所有権を呼び出し元に返す
}
借用のルール
// ルール1: 不変借用は複数可能
let s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{}, {}", r1, r2); // OK
// ルール2: 可変借用は1つのみ
let mut s = String::from("hello");
let r1 = &mut s;
// let r2 = &mut s; // エラー!同時に2つの可変借用
r1.push_str("!");
// ルール3: 不変と可変の同時借用は不可
let mut s = String::from("hello");
let r1 = &s;
// let r2 = &mut s; // エラー!r1がまだ使用中
println!("{}", r1);
let r2 = &mut s; // OK: r1は以降使用されない(NLL)
よくある間違い
1. 移動後の使用
// 間違い
let s1 = String::from("hello");
let s2 = s1;
println!("{}", s1); // エラー!
// 正しい: clone を使用
let s1 = String::from("hello");
let s2 = s1.clone();
println!("{}", s1); // OK
2. ダングリング参照
// 間違い: ローカル変数への参照を返す
fn dangle() -> &String {
let s = String::from("hello");
&s // s はスコープを抜けると破棄される
}
// 正しい: 所有権を返す
fn no_dangle() -> String {
let s = String::from("hello");
s // 所有権を移動
}
3. ライフタイムの不整合
// 間違い: ライフタイムが短い参照を返す
fn bad_longest<'a>(x: &'a str, y: &str) -> &'a str {
y // エラー!y のライフタイムは 'a と無関係
}
// 正しい: 両方に同じライフタイムを付与
fn good_longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
ライフタイム省略規則
// 規則1: 各入力参照に個別のライフタイム
fn foo(x: &i32, y: &i32) -> ...
// => fn foo<'a, 'b>(x: &'a i32, y: &'b i32) -> ...
// 規則2: 入力が1つなら出力も同じライフタイム
fn foo(x: &i32) -> &i32
// => fn foo<'a>(x: &'a i32) -> &'a i32
// 規則3: &self があれば出力はそのライフタイム
impl Foo {
fn bar(&self, x: &i32) -> &i32
// => fn bar<'a, 'b>(&'a self, x: &'b i32) -> &'a i32
}
パフォーマンス考慮事項
不要なcloneを避ける
// 悪い: 不要なクローン
fn process(data: &Vec<String>) -> Vec<String> {
data.clone()
.into_iter()
.filter(|s| !s.is_empty())
.collect()
}
// 良い: 借用を使用
fn process(data: &[String]) -> Vec<&str> {
data.iter()
.filter(|s| !s.is_empty())
.map(|s| s.as_str())
.collect()
}
スタック vs ヒープ
// スタック(高速)
let x: i32 = 5; // Copy トレイト
// ヒープ(動的)
let s: String = String::from("hello"); // Move
// スライスは借用(コピーなし)
let slice: &str = &s[..]; // ポインタと長さのみ
発展トピック
内部可変性
use std::cell::RefCell;
// 不変参照から可変借用を取得
let data = RefCell::new(5);
*data.borrow_mut() += 1;
// 注意: 実行時に借用ルールをチェック
スマートポインタ
use std::rc::Rc;
// 複数の所有者
let a = Rc::new(5);
let b = Rc::clone(&a);
let c = Rc::clone(&a);
// 参照カウント: 3
// 最後の Rc が破棄されると値も破棄
Cow (Clone on Write)
use std::borrow::Cow;
fn process(data: Cow<str>) -> Cow<str> {
if data.contains("bad") {
// 変更が必要な場合のみクローン
Cow::Owned(data.replace("bad", "good"))
} else {
// 変更不要なら借用のまま
data
}
}