LoginSignup
6
7

More than 3 years have passed since last update.

犬でもわかる入門Rustのライフサイクル

Last updated at Posted at 2019-09-04

はじめに

これはRustのライフサイクルを犬でもわかるように纏めてみる試みである

ライフサイクルとは

全ての変数にはライフサイクルがある
これは以下のようなダングリング参照を防ぐ為にある
これを防ぐと何が嬉しいかと言うと、解放済みオブジェクトに対してアクセスしてパニックになるような自体等を防げる

{
  let r;                // ---------+-- 'a
                        //          |
  {                     //          |
    let x = 5;          // -+-- 'b  |
    r = &x;             //  |       |
  }                     // -+       |
                        //          |
  println!("r: {}", r); //          |
}                       // ---------+

このコードがなんでいけないかというと、xの参照をrに入れているけど、xのライフタイムはlet xされたブロックの中だけである。
なので、rにxの参照を入れられてしまうと、printlnのタイミングで、解放済みのxへ参照をしようとしてランタイムエラーになる。
そのような事を防ぐために、ライフタイムという仕組みはある。

※ ちなみにライフタイムの仕組みによってそもそもこのコードはコンパイルエラーになる

ライフタイム解説

ドキュメントにはこう書かれている。

コンパイラはライフタイムに3つの規則しかない。

コンパイラは3つの規則を活用し、明示的な注釈がない時に、参照がどんなライフタイムになるかを計算します。 最初の規則は入力ライフタイムに適用され、2番目と3番目の規則は出力ライフタイムに適用されます。 コンパイラが3つの規則の最後まで到達し、それでもライフタイムを割り出せない参照があったら、 コンパイラはエラーで停止します。

最初の規則は、参照である各引数は、独自のライフタイム引数を得るというものです。換言すれば、 1引数の関数は、1つのライフタイム引数を得るということです: fn foo<'a>(x: &'a i32); 2つ引数のある関数は、2つの個別のライフタイム引数を得ます: fn foo<'a, 'b>(x: &'a i32, y: &'b i32); 以下同様。

2番目の規則は、1つだけ入力ライフタイム引数があるなら、そのライフタイムが全ての出力ライフタイム引数に代入されるというものです: fn foo<'a>(x: &'a i32) -> &'a i32。

3番目の規則は、複数の入力ライフタイム引数があるけれども、メソッドなのでそのうちの一つが&selfや&mut selfだったら、 selfのライフタイムが全出力ライフタイム引数に代入されるというものです。 この3番目の規則により、必要なシンボルの数が減るので、メソッドが遥かに読み書きしやすくなります。

ライフタイムの例

1つ目の規則の例が以下のコードだ。

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}

これは返り値が受け取った引数と同じなのが明白だ。
これは以下のコードと等価である。

fn first_word<'a>(s: &'a str) -> &'a str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}

この関数がライフタイム注釈なしでコンパイルできた理由は、歴史的なものです: 昔のバージョンのRust(1.0以前)では、 全参照に明示的なライフタイムが必要だったので、このコードはコンパイルできませんでした。 その頃、関数シグニチャはこのように記述されていたのです:

2つ目の規則の例がこれだ。

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
  if x.len() > y.len() {
      x
  } else {
      y
  }
}

これは、先程みたいにこうライフタイム注釈を省略して書けない。

fn longest(x: &str, y: &str) -> &str {
  if x.len() > y.len() {
      x
  } else {
      y
  }
}

この関数の戻り値のライフタイムはx、yどちらのライフタイムになるのでしょうか?
わかりません。
なので、明示的に同じライフサイクル注釈を使うことにより、xとyのうち、短い方のライフタイムに依存するようになります。

なので、以下のコードはコンパイルエラーになります

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
  if x.len() > y.len() {
      x
  } else {
      y
  }
}

fn main() {
  let a = String::from("hello world");
  let result;

  {
    let b = String::from("bye");
    result = longest(&a, &b);
  }

  println!("{}", result);
}

3つ目の規則は省略する。
selfに依存するのは、まぁどの言語もそうだし当たり前だよね。

参考リンク

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