イミュータブル・ミュータブルとは?
例えば、プログラミング言語のRustでは、メモリ管理について、所有権・借用・ライフタイムという独特な仕組みを採用しています。
そのため、開発する上で制約の多いOSやコンパイラなどのソフトウェアを開発することができますし、高速に動くプログラムを書くことができます。
この独特なメモリ管理の借用について説明します。
まず、借用の前に、変数におけるイミュータブル、ミュータブルについて説明します。
イミュータブルな変数は、変数を作ったあとに、変数の中身を変更できません。
逆にミュータブルな変数は、あとから変数の中身を変更できます。
これを前提知識に借用の説明をします。
借用とは
借用とは所有権を移さず、変数の中身を一定期間だけレンタルすることです。
借用は3つのルールがあります。
1つ目は、イミュータブルな借用なら複数できること。
2つ目は、ミュータブルな借用なら1つしかできないこと。
3つ目は、イミュータブルな借用とミュータブルな借用は同時にできないことです。
コードを書きながらみていきましょう。
イミュータブルな複数の借用とは
借用とは所有権を移さず、変数の中身を一定期間だけレンタルすることです。
Rustでは、イミュータブルな借用なら複数できます。つまり、変更不可な借用は何個でも作れます。
RustとJavaのコードを比較して、借用の特徴を見ていきましょう。
まずJavaのコードを書いてみます。
-Java- public static void main(String[] args) { String a = new String(""hello""); String b = a; System.out.println(a); System.out.println(b); } |
このところで、変数bに変数aを代入しています。
コンパイルして実行してみましょう。
hello hello |
aにもbにも「hello」が入っているので、helloが2つ表示されました。
次に、Rustのコードを書いてみましょう。
main(){ let a = "hello".to_string(); let b = a; //★ println!("{}", a); println!("{}", b); } |
所有権の動画で説明しましたが、何もしなければエラーになります。
error[E0382]: borrow of moved value: |
イミュータブル(変更不可)な借用は変数の前に & を付けます。
fn main(){ let a = "hello".to_string(); let b = &a; println!("{}", a); println!("{}", b); } |
コンパイルして実行します。
hello hello |
helloが2つ表示されました。
所有権のムーブではなく、借用であればエラーになりません。
なぜなら、変数aに所有権が残っているからです。
これでJavaと同じ書き方ができました。
ミュータブルな1つだけの借用とは
Rustの前に、Javaの場合をみてみましょう。
Javaの場合は、ミュータブルな借用は複数できます。
配列yと配列zを作成しています。
これらは両方ミュータブルな参照です。
-Java- public static void main(String[] args) { int[] x = new int[3]; int[] y = x; int[] z = x; for (int i = 0; i < 3; i++) { y[i] = i + 1; z[i] = i * 2; } for (int val : x) { System.out.print(val + " "); } } |
コンパイルして実行してみましょう。
0 2 4 |
yで変更した内容が、zの内容で上書きされてしまいました。
一方、Rustの場合は、ミュータブルな借用は、複数することができません。
コードを書いて試してみましょう。
fn main(){ let mut x = vec![0, 0, 0]; let y = &mut x; let z = &mut x; for i in 0..3 { y[i] = i + 1; z[i] = i * 2; } for var in x { print!("{}", var); print!("{}", " "); } } |
ミュータブルな借用の場合、変数の前に &そのあとにmut をつけます。
実行してみましょう。
cannot borrow (直訳:一度に複数回「x」を可変として借りることはできません。) |
Rustだと、ミュータブルな借用を複数作ろうとするとエラーになります。
一方、ミュータブルな借用を1つだけしてみます。
fn main(){ let mut x = vec![0, 0, 0]; let z = &mut x; for i in 0..3 { z[i] = i * 2; } for var in x { print!("{}", var); print!("{}", " "); } } |
エラーにならずxの配列を表示させることができました。
Rustには、このような厳格なルールがあることので、Javaのような、あとからzでデータを上書きしてしまうような問題が起こりません。
最後のルールの「借用は同時にできない」のルールをみていきましょう。
このコードはyがミュータブルな借用、zがイミュータブルな借用をしようとしています。
コンパイルして実行してみます。
fn main(){ let mut x = vec![0, 0, 0]; let y = &mut x; let z = &x; for i in 0..3 { y[i] = i + 1; } for var in x { print!("{}", var); print!("{}", " "); } }
|
エラーとなります。
エラーを訳すと、「xは可変としても借りられているので、不変として借りることができません」という意味です。
これが、ミュータブルな借用と、イミュータブルな借用は同時にできないというルールです。