こんちには。キノコードです。
このレッスンでは、 ライフタイムについて説明をします。
▼ YouTube動画はこちらからどうぞ。
ライフタイムとは?
プログラミング言語の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}; 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]: (直訳:エラー[E0597]: |
変数aを宣言。
コードブロックの中に、変数bを宣言、初期化。
Rustの場合は、参照ではなく、借用です。
アンパーサンドをつけて、イミュータブル(変更不可)な借用をします。
aを表示させてみましょう。
コンパイルエラーとなりました。
これは、コードブロックを抜けるので、メモリのリソースが解放されるためです。
メモリ解放後のリソースを参照してしまうことになるため、エラーになります。
まとめると、
Javaの場合はコードブロックを抜けても、参照型だと、全ての参照がなくなるまでメモリに残ったままになります。
ちなみに、プリミティブ型の場合は、コードブロックを抜けるとリソースが解放されます。
一方、Rustの場合はコードブロックを抜けたらリソースは解放されます。借用されていてもメモリに残りません。
これが「借用において所有者のスコープより長く存続できない」というライフタイムのルールなのです。
Rustではこのような仕様にして、メモリの安全を確保しているのです。