Ccmmutty logo
Commutty IT
2
7 min read

Rust ライフタイムの基本的なところ

https://cdn.magicode.io/media/notebox/43b8b0a4-f828-4b2f-b63f-fcbefe2e05db.jpeg

ライフタイムとは

すべての借用に問題がないことを確認する仕組みです.
以下&を用いて変数を借用する場合の例です.
fn main() {
    let i = 3; // Lifetime for `i` starts.
    {
        let borrow1 = &i; // `borrow1` lifetime starts.
        print!("borrow1: {}", borrow1);
    }
    {
        let borrow2 = &i; // `borrow1` lifetime starts.
        print!("borrow2: {}", borrow2);
        // `borrow2` ends
    }
}

明示的なアノテーション

foo<'a>
これは以下の2つを明示します.
  • foo'aというライフタイムパラメータを持ちます.
  • fooのライフタイムは'aのライフタイムを超えることはないです.
ライフタイムが2つある場合
foo<'a, 'b>
これは以下の2つを明示します.
  • foo'a'bいうライフタイムパラメータを持ちます.
  • fooのライフタイムは'a'bのライフタイムを超えることはないです.

型を明示した書き方

型を明示した場合'a&'a Tとなります.

ライフタイムを明示的に書く例

print_refsが取る2つのi32の参照はそれぞれライフタイム'a, 'bを持ちます. これらのライフタイムはprint_refよりも短くなることはないです.
fn print_ref<'a, 'b>(x: &'a i32, y: &'b i32) {
    println!("x is {} and y is {}", x, y);
}

fn main() {
    let (four, nine) = (4, 9);

    print_ref(&four, &nine);
}
以下のコードはエラーになります.
fn failed_borrow<'a>() {
    let _x = 12;

    let _y: &'a i32 = &_x;
//          -------   ^^^ borrowed value does not live long enough
}

fn main() {
    failed_borrow();
}
これは&_xのライフタイムは'aのライフタイム以上になることはないためです.
_xのライフタイムを以下のように明示すればエラーは起きません.
fn failed_borrow<'a>() {
    let _x: &'a i32 = &12;
    let _y: &'a i32 = &_x;
}

ダングリング参照

ダングリング参照とは無効なメモリ領域を参照することです.
{
        let r;
        {
            let x = 5;
            r = &x;
//              ^^ borrowed value does not live long enough
        }
//      - `x` dropped here while still borrowed
        println!("r: {}", r);
//                        - borrow later used here
    }
以下のように書いたら通ります.
fn borrow<'a>() {
    {
        let r: &'a usize;
        {
            let x: &'a usize= &5;
            r = &x;
        }
        println!("r: {}", r);
    }
}

ジェネリックなライフタイム

コンパイラはleftの返り値がx, yどちらのライフタイムを持つのかわかりません.
したがって以下はエラーになります.
fn left(x &str, y: &str) -> &str {
    return x
}

fn main() {
    print!(left());
}
以下のように書いたら通ります.
fn left<'a>(x: &'a str, y: &'a str) -> &'a str {
    return x
}

fn main() {
    print!("{}", borrow(&"l", &"r"));
}

ライフタイムの省略

こちらは通ります
fn only_left(x: &str) -> &str {
    x
}
コンパイルはライフタイム省略規則というライフタイム注釈を省略できる規則を持っています.
  • 関数やメソッドの引数のライフタイムは、入力ライフタイム
  • 戻り値のライフタイムは出力ライフタイム
と呼ばれます.

ライフタイム省略の3つの規則

  1. 参照である各引数は、独自のライフタイム引数を得る
    • (言い換えれば, 2つ引数のある関数は、2つの個別のライフタイム引数を得るということ)
  2. 1つだけ入力ライフタイム引数があるなら、そのライフタイムが全ての出力ライフタイム引数に代入される
  3. 複数の入力ライフタイム引数があるけれども、メソッドなのでそのうちの一つが&selfや&mut selfだったら、 selfのライフタイムが全出力ライフタイム引数に代入される

異なるライフタイムを持つ引数への参照

以下はエラーになります.
fn main() {
    let l = String::from("l");
    let res;
    {
        let r = String::from("r");
        res = borrow(&l, &r);
//                       ^^ borrowed value does not live long enough
    }
//  - `r` dropped here while still borrowed
    print!("{}",res);
//              --- borrow later used here
}
rはクロージャを抜ける際に解放されます.

構造体定義のライフタイム注釈

struct Core<'a> {
    part: &'a str,
}
Core'aをもつpartよりも長生きしないことを意味します.

静的ライフタイム

参照がプログラムの全期間生存できる事を意味します.

文字列リテラルは全て'staticライフタイムになる

let s: &'static str = "I have a static lifetime.";

ライフタイムの圧縮

// `'static`ライフタイムを持つ定数を作成
static NUM: i32 = 18;

// `NUM`への参照を返す。ライフタイムは`'static`から引数の
// ライフタイムへと圧縮されている。
fn coerce_static<'a>(_: &'a i32) -> &'a i32 {
    &NUM
}

fn main() {
    let coerced_static: &i32;
    {
        // エラー
        let num = 8;
        coerced_static = coerce_static(&num);

        // こっちは通る
        // coerced_static = coerce_static(&8);
        // coerced_static = coerce_static(&NUM);
    }
    print!("{}", coerced_static)
}

Trait bound

関数が非静的な参照は受け取らないことを明示できます.
ライフタイムも一種のジェネリクス型であるので境界を与えることができます.
use std::fmt::Debug;

fn print_it( input: impl Debug + 'static ) {
    println!( "'static value passed in is: {:?}", input );

    }

fn main() {
    // i is owned and contains no references, thus it's 'static:
    let i = 5;
    print_it(i);

    // &i は main() のスコープで定義されたライフタイムしか持ちません.
    // したがって'staticではありません.
    print_it(&i);
}

asyncのライフタイム

_ について

  • _ は即座に解放されます.
  • 逆に名前をつけると即座に解放されずデッドロックすることがあります.
let _flag = val.read().unwrap();
// _flagにReadロックからリターンされた値を保持しています.
// したがってこの変数のスコープが外れるまでロックが開放されません.
*val.write().unwrap() = false;
// Writeロックを獲得しようとするとデッドロックとなってしまいます.
let _ = val.read().unwrap();
 // _ という変数に保持された値は即座に破棄されます.
// したがってReadロックは即座に解放されます.
*val.write().unwrap() = false;

参考

Discussion

コメントにはログインが必要です。