ライフタイムとは?|プログラミングにおけるライフタイムについて3分でわかりやすく解説します【プログラミング初心者向け】

【用語解説】プログラミング言語関連

こんちには。キノコードです。
このレッスンでは、 ライフタイムについて説明をします。

▼ YouTube動画はこちらからどうぞ。

この記事の執筆・監修

キノコード
キノコード

テクノロジーアンドデザインカンパニー株式会社のCEO。
日本最大級のプログラミング教育のYouTubeチャンネル「キノコード」や、プログラミング学習サービス「キノクエスト」を運営。
書籍「あなたの仕事が一瞬で片付くPythonによる自動化仕事術」の著者や、雑誌「日経ソフトウエア」の寄稿などの実績も多数。

ライフタイムとは?

プログラミング言語のRustでは、メモリ管理について、所有権・借用・ライフタイムという独特な仕組みを採用しています。
そのため、開発する上で制約の多いOSやコンパイラなどのソフトウェアを開発することができますし、高速に動くプログラムを書くことができます。
この独特なメモリ管理のライフタイムについて説明します。
ライフタイムとは、「借用において所有者のスコープより長く存続できない」というルールです。。

JavaとRustの比較

この意味をRustとJavaのコードを比較して、ライフタイムの特徴を見ていきましょう。
借用については別の解説動画をご覧ください。
まず、Javaで参照型の配列を作っていきましょう。

public static void main(String[] args) {

    int[] a; //1.変数aを宣言

    {

        int[] b = {5}; //2.変数bを初期化

        a = b; //3.aにbの参照をコピーする

        b = null; //4.ここでbのリソースが解放「されない」

    }

    System.out.print(a[0]); //5.aを参照

}

-結果- 5

変数aを宣言
次にコードブロックを書きます。
コードブロック内にローカル変数を宣言すると、そのコードブロック内のみローカル変数を使うことができます。
次に、配列bを宣言して初期化。
aにbの参照をコピーします。
bにnullを代入します。
このコードブロックの外にaが表示されるプリントラインを記述しましょう。
bはコードブロック内でのみ使えるはずです。
つまり、bのリソースは解放されているはずです。
bの参照をコピーしたaはどうなるのでしょうか?
コンパイルして実行します。
5が表示されました。
Javaの場合はaを参照できます。
これは、なぜでしょうか?
Javaの場合は「全ての参照がなくなるまで、メモリにデータを残し続ける」ルールがあるためです。

Javaの場合

public static void main(String[] args) {

    int[] a; //1.変数aを宣言

    {

        int[] b = {5};
        a = b;

        System.out.println("aの番地 : "+a.hashCode());

        System.out.println("bの番地 : "+b.hashCode());

    }

}

では、この時点でデータの入っているメモリの番地を調べてみましょう。
aとbのメモリの番地を表示させてみます。
このように同じ番地になっています。

public class Lifetime03{

    public static void main(String[] args) {

        int[] a;

        {

            int[] b = {5};

            a = b;

            System.out.println("aの番地 : "+a.hashCode());

            System.out.println("bの番地_1回目 : "+b.hashCode());

            b = null;

            System.out.println("aの番地 : "+a.hashCode());

            System.out.println("bの番地_2回目 : "+b.hashCode());

        }

    }

}

bにnullを代入した後はどうなるでしょう?
ここで、bにnullを代入するので、参照できなくなります。
一方、aは参照できます。
実行してみましょう。
変数aの参照が残っているので、メモリにデータが入ったままを維持できています。
一方、bは1回目の番地は表示されますが、2回目の番地は表示されません。
次に、Rustの場合を見てみましょう。
別の解説動画で説明しましたが、Rustの場合は参照ではなく、借用です。

Rustの場合

fn main(){

    let a; // 1.変数aを宣言

    {

        let b = 5; // 2.変数bを初期化

        a = &b; // 3.bをaに借用

    } // 4.ここでbのリソースが解放される

    println!("{}", a); // 5.aを参照

}

-結果-

error[E0597]: b does not live long enough

(直訳:エラー[E0597]: bは十分に長寿ではありません) 

変数aを宣言。
コードブロックの中に、変数bを宣言、初期化。
Rustの場合は、参照ではなく、借用です。
アンパーサンドをつけて、イミュータブル(変更不可)な借用をします。
aを表示させてみましょう。
コンパイルエラーとなりました。
これは、コードブロックを抜けるので、メモリのリソースが解放されるためです。
メモリ解放後のリソースを参照してしまうことになるため、エラーになります。
まとめると、
Javaの場合はコードブロックを抜けても、参照型だと、全ての参照がなくなるまでメモリに残ったままになります。
ちなみに、プリミティブ型の場合は、コードブロックを抜けるとリソースが解放されます。
一方、Rustの場合はコードブロックを抜けたらリソースは解放されます。借用されていてもメモリに残りません。
これが「借用において所有者のスコープより長く存続できない」というライフタイムのルールなのです。
Rustではこのような仕様にして、メモリの安全を確保しているのです。

KinoCode チャンネル

YouTubeで毎日動画配信しています。
動画は3分間なので、
 ・通勤時間
 ・お昼休み
 ・お手すきのとき
 ・寝る前
など手軽に視聴できます。
 
ちょっとしたインプットにどうぞ!
 
▼チャンネル登録はこちらからどうぞ。