チュートリアル

Rust所有権:所有権の基本

Rust所有権借用応用
広告エリア

所有権とは

Rustはガベージコレクタなしでメモリ安全性を実現するため、所有権システムを採用しています。

所有権のルール

  1. 各値は所有者(変数)を持つ
  2. 所有者は同時に1つだけ
  3. 所有者がスコープを抜けると値は破棄される
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つだけ
  • ライフタイムで参照の有効期間を保証
  • コンパイラがメモリ安全性を保証

次回はエラー処理について学びます。

広告エリア