10
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rustの所有権/借用/ライフタイム【備忘録】

Posted at

TL; DR

Rustの用語集【備忘録】でWIPにしていた下記を調べた備忘録です。

  • 所有権
  • 借用
  • ライフタイム

自分用のメモを流用しただけなので、わかりにくい部分あると思いますがご了承ください。

共通する概念

Rustは思想として安全性スピードを最重要視しており、それらをゼロコスト抽象化を用いて実現可能にしている。具体的にはコンパイル時に型ごとに関数をあらかじめ解決するよう静的ディスパッチを使用するため、実際の実行時にはオーバーヘッドなく関数を呼び出すことができる。
※動的ディスパッチができないわけではない。

use std::fmt::Debug;
fn main() {
    fn static_dispatch<T: Debug>(x: T) {
        println! {"{:?}", x};
    }
    static_dispatch(2020);
    static_dispatch("thursday");
    
    #[derive(Debug)]
    struct Point {
        x: i32,     // xは32ビット符号あり数値として指定
        y: String,  // yは文字列として指定
    }
    let points = Point { x: 2020, y: "thursday".to_string() };
    static_dispatch(points);
}
========================result========================
$ ./main
2020
"thursday"
Point { x: 2020, y: "thursday" }

所有権

変数束縛において、Rustは所有権という特性を持つ。

fn ownership() {
    let origin = vec![1, 2, 3];
}

上記のような変数originがあった場合、新しくVec<T>が作られる。変数originがスコープに入った場合各要素のためにヒープにメモリが割り当てられる。しかし、このスコープを抜けるとRustは割り当てたリソース自動的に解放してくれる。その際注意しなければいけないのは別のリソースから参照されている場合、参照元が先にスコープを抜けてしまうと参照することができなくなってしまう。必ず有効期限が参照元 > 参照が成り立つように設計しなければ、意図せぬ動作が発生してしまう。

ムーブセマンティクス

変数束縛はリソースに対して常に対になることを保証する。

fn ownership() {
    let origin = vec![1, 2, 3];
    let duplicate1 = origin;

    println!("origin[0] first index is {}", origin[0]);
}
======================result======================
$ rustc main.rs
error[E0382]: borrow of moved value: `origin`
 --> main.rs:5:45
  |
2 |     let origin = vec![1, 2, 3];
  |         ------ move occurs because `origin` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait
3 |     let duplicate1 = origin;
  |                      ------ value moved here
4 | 
5 |     println!("origin[0] first index is {}", origin[0]);
  |                                             ^^^^^^ value borrowed here after move

error: aborting due to previous error

この場合は既にduplicate1のリソースとしてoriginが束縛されているため、後続のprintlnメソッドにリソースとして割り当てることができないことがわかる。
Rustのデフォルトの動作として、ベクタを別の変数に束縛した場合そのデータへのポインタをコピーするが、ポインタが2つあることによるデータの競合を防ぐためにコピー元(上の例だとorigin)へのポインタの使用を制限する。

Copy型

指定した型のトレイトにCopyがある場合は上記に当てはまらず、ポインタのみでなくデータごとコピーするため元の束縛された変数で呼び出すことが可能。
下記の例の場合、boolean型はcopyトレイトを持っているためprintln!("{}", origin);で呼び出すことを可能にしている。

bool
fn main() {
    let origin = false;

    let duplicate1 = full_copy(origin);
    println!("{}", origin);
}

fn full_copy(x: bool) -> bool {
    !x
}

その他所有権のパターン

success pattern
// スコープ外での書き込み
fn out_of_scope() {
    let mut origin = vec![1, 2, 3];
    let mut duplicate1 = &mut v1;
    {
        let duplicate2 = &duplicate1;
    }
    *duplicate1 = vec![4, 5, 6];
}

// 複数階層の参照
fn multi_ref() {
    let origin = vec![1, 2, 3];
    let duplicate1 = &origin; 
    let duplicate2 = &duplicate1;

    println!("Hello, {}, {}, {} !", origin[0], duplicate1[1], duplicate2[2]);
}
failure pattern
// 複数の参照先
fn mut_single() {
    let mut origin = vec![1, 2, 3];
    let mut duplicate1 = &mut origin;
    let mut duplicate2 = &mut origin;
}

// 既に参照先がある場合(同上)
fn impossible_refs() {
    let mut origin = vec![1, 2, 3];
    let mut duplicate1 = &mut origin;
    let duplicate2 = &origin;
}

// ミュータブルで参照しているベクタへの書き込み
fn mut_readonly() {
    let mut origin = vec![1, 2, 3];
    let mut duplicate1 = &mut v1;
    let duplicate2 = &duplicate1;
    *duplicate1 = vec![4, 5, 6];
}

借用

&T型で所有権の借用が可能になる。
借用された変数&Tの束縛はスコープから外れてもリソース解放されないため、後続で値を変更することができない。

borrowed
fn borrowed_func(v: &Vec<i32>) {
     v.push(5);
}

let v = vec![];     // このベクタへの変更は無効になる

borrowed_func(&v);  // エラーが吐かれる

借用を利用する際は、以下の借用についてのルール4点を意識する必要がある。

  • 借用は全て所有者のスコープより長く存続することはできない。
  • 上述した&Tと後述する&mutについては同時に持つことはできない。
  • &Tは書き込みが発生しない場合は何回でも利用できる。
  • &mutは同時に複数持つことはできない。

&mut参照

変数を指定する際にmutを置くことでミュータブルな変数束縛であることを宣言できたが、参照する場合も同様に&mutと宣言しなければミュータブルなものとして変数を束縛することができない。またミュータブルに参照している変数を利用する場合は*Tとして記述しなければミュータブルな変数束縛として扱うことができない。

mutable_refs
// OKパターン
fn main() {
    let mut x = 5;
    {
        let y = &mut x;
        *y += 1;
    }
    println!("{}", x);  // 6が返ってくる
}

// NGパターン
fn main() {
    let mut x = 5;
    {
        let mut y = x;
        y += 1;
    }
    println!("{}", x);  // 5が返ってくる
}

スコープの考え方

スコープの範囲は{}でくくられた範囲に限定される。言い換えればその中では変数束縛が継続されリソースが解放されることがないということ。
&Tもしくは&mutの一方しか持たない場合は問題ないが、両方持つ場合はスコープを意識する必要がある。

fn main() {
    let mut x = 5;
    {                    // &mutとしてxを借用開始
        let y = &mut x;
        *y += 1;
    }                    // &mutとしてのxを借用終了
    println!("{}", x);   // xを借用
}

ライフタイム

参照における有効期限(ライフタイム)は、すべての参照に対し発生するがコンパイラがよしなにしてくれるため省略することが可能。
反対に明示的にライフタイムは'aを用いて記述することも可能であり、指定することができる。
下記の例は記述は異なるが意味は同じなる。(ライフタイムの省略をしているか否かの違い)
fn foo(x: &i32) {} = fn bar<'a>(x: &'a i32) {}
上記の関数のあとの<>ジェネリックパラメータを意味し、ライフタイムはその一種である。

static

'staticがついたライフタイムには2通りあり、いずれも特別な扱いがされる。
文字列リテラルは&'static str型をもち、常に参照が有効になる。
もう一つはグローバルに参照される変数(≒ 定数)に利用され、その場合型を明示する必要がある。

// 文字列
let x: &'static str = "Hello, world.";

// グローバル
static FOO: i32 = 5;
let x: &'static i32 = &FOO;

ユースケース的なやつ

struct の中

struct
struct Hoge<'a> {           // structに対してライフタイムを明示する
    x: &'a i32,             // 変数xがそのライフタイムを利用するように明示する
}

fn main() {
    let y = &2;
    let f = hoge { x: y };  // structを参照する際はi32型として読みとる

    println!("{}", f.x);
}

impl ブロック

impl
struct Hoge<'a> {
    x: &'a i32,
}

impl<'a> Hoge<'a> {                    // メソッドに対するライフタイムを明示 & Hogeがそのライフタイムを利用する
    fn x(&self) -> &'a i32 { self.x }
}

fn main() {
    let y = &2;
    let f = Hoge { x: y };

    println!("x is: {}", f.x());
}

複数のライフタイム

multiple
fn x_or_y<'a>(x: &'a str, y: &'a str) -> &'a str {      // xもyもライフタイムaを使うことができる

}

fn x_or_y<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {  // xはライフタイムaでyはライフタイムbと使い分けることができる

}
10
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?