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
    }
}