本記事の目的
-
- Rustの所有権について知ることでメモリについて理解できるようになる。
-
- &strとStringの違いについて理解できる。
- メモリ管理について理解できる。
C言語とGCの比較
1文字に対して1万バイトのメモリを確保し、それを一万回行うことで合計で100メガバイトのメモリを確保するというコードを書いてみるとする。
Python
for _i in range(10000):
p = [b' '] * 10000
C言語だとどうなるだろうか?
#include <stdlib.h>
int main() {
for (int i = 0; i < 10000; i++) {
char* p = (char*)malloc(10000);
// ここでfree(p)として開放しなければならない
}
return 0;
}
C言語コードの何が問題か気づけるだろうか?
Cのコードはメモリを解放し忘れている。メモリが圧迫されてバグを起こしてしまう可能性がある。他方で、Pythonは動く。GC(ガべーレージコレクション)を採用しているためだ。GCとは実行中のプログラムが占有していたメモリ領域のうち不要になったものを自動的に検知し解放し、空き領域として再利用できるようにする仕組みのことである。
Rustだとどう書けるだろうか
fn main() {
for _i in 0..10000 {
let p = vec![b' '; 10000];
}
}
GCを用いる言語とRustの所有権
一見するとGCに見える。しかし、実際は所有権という概念が使われている。所有権とは変数がその数値に対して責任を持つということであり下記の三つを意味する。
Rustの所有権についての三つの原則
1.Rustの各値は「所有者」と呼ばれる変数と対応している
2.一度に存在できる所有者は1人だけ
3.所有者がスコープから外れたら値は破棄される
上記の原則をRustのコードと照らし合わせてみると、
1. pは右辺10000バイトに対して責任を持つ。
2. Pは一つだけしか存在できない
3. スコープ{}を出ると値を破棄される。このことからRustはメモリを意識しないせずに動くことが可能となる。
しかしこの場合 GCと所有権の違いについてわかり辛いので下記のコードで説明する。
下記のコードは
data_inputという関数で標準入力を受け取ってそれをmain関数で表示している。
Python
def main():
a = data_input()
print(a)
def data_input():
a = input()
return a
main()
Rustで書いてみる
fn main() {
let text = String::from("こんにちは");
print_text(text);
println!("{}", text); // エラー: 所有権が移動している
}
fn print_text(text: String) {
println!("{}", text);
}
これはコンパイルエラーで動かない。
main関数で処理を行うのだが、textがprint_text関数に移動してしまい、print_text関数でしか使えず、スコープ{}を出るとメモリを解放されてしまう。そのためmain関数内でprint_text関数が使えなくなっているのだ。
では所有権という特性を持ったまま関数を使えるようにするにはどうすればよいのだろうか?借用という方法を使えば良い。具体的にはString→&strにすれば良い。
fn main(){
let text = String::from("こんにちは");
print_text(&text); // textの参照を貸す
}
fn print_text(text: &str){ // &str型の引数を受け取る
println!("{}", text);
}
これは&を使うことで、所有権をmain関数で持ったまま、print_text関数に一時的に借用されて、スコープを出たら所有権はmain関数に返されるのだ。そのためmain関数で使用が可能となる。