Sunday, December 27, 2020

Rust の ownership の仕組みについて今ざっくり知ってる範囲を説明してみる



Rustは何年か前からぽちぽちいじっていて、Haskell並みに好きな言語ではあったが、

(こんなのとか作ってた) https://github.com/keitaroemotion/stress-tester 


そもそもRustって何がいいの?っていう話になるとやはりパフォーマンスで圧倒的だとか、安全性とかがある。とくに並列処理とかマルチスレッドをわりと簡単に安全に作れるところとか。


ただ、初学者にはRustの根本であるオーナーシップの仕組みがわかりにくいんじゃないか、という懸念はある。なぜRustが革命的かというと、これはヒープに起因する問題で、まずこれまではヒープを扱うのに主に2つのアプローチがあった。


(1) GC(ガーベジコレクタ、別名お掃除ママ)

(2) てめーの部屋はてめーで片付けろや (Cとかの手動メモリ管理)


(1) はヒープをランタイムに勝手に(JavaだとJVMとか)が整理してくれて、使わないメモリ領域を勝手に解放、他に割り当て、とかをやってくれる、やさしいマンマ。ごはんよぉぉ〜


(2) そうでなければ、自分でメモリ領域を割り当てる (malloc)、そのあと freeで解放、みたいなことを自分でやっていくが、ミスると segmentation fault とか容赦ないし、まあお漏らしして無事死亡。


さて、こうやってプログラミング言語は維持されてきて、そして、(1)はまあお掃除ママは優しいんだけどそれはすなわちお掃除ママががんばらなきゃいけないってことで、当然遅かったりメモリに響いたりしていた。

そこにRustが爆誕した。

Rustのアプローチは(1)(2)とは全く異なり、

(3) それぞれの値に死神(owner)を必ずつけて、値が生まれてから死ぬまでの一生をルールで縛ろう、ということになった。

例えば、


let s1 = String::from("Hi");

let s2 = s1;

println!("{}", s1);


こんなのがあったとする。

すると、普通のプログラミング言語だと s2 というのはs1のそっくりコピーさんなわけで、


こんなかんじを想像するだろうけど、そうではない。実際は下だ。


左はポインタ含めて、データのインスタンスを示すメタデータだが、Stringなどの非スカラーデータは2回目別の値にアサインされるとこの二番目の図のように、メタデータだけがコピーされ、同じインスタンスを指し示す。

メタデータだけのコピーになることを、shallow copy(浅いコピー)といい、インスタンスまでコピーする場合は deep copy(深いコピー)などと言われる。

ただし、メタデータだけのコピーになるが、上記コードでの、

println!("{}", s1);

はコンパイルエラーとなる。なぜかというとs1, s2ふたつのポインタが同一インスタンスをさすと、例えば片方s1がメモリを解放した時s2はどうなるんや問題があり、Rustのきまりとして、このように二つ目のメタデータが同一インスタンスをさしたときは、初めのインスタンスの寿命は消える


s1の死神 「s1、もうs2がお前の業務を引き継いでいる。お前に用はない。さあ、逝くぞ!」


というこういうかんじである。このシビアな言語仕様のおかげで、メモリ使用率やパフォーマンスを相当改善できている、というのがRustの強みである。

ただし、はじめの図みたいに deep copyをどうしてもしたければ、clone()というメソッドがあるので、 let s2 = s1. clone(); みたいにすれば許してくれはする。

ownershipは、スコープにより変数の寿命が死神によって決められているというふうに言い換えることもできる。


また、注意しなくてはいけないのは、スカラーデータ(数値、ブーリアン、スカラーデータだけのタプル、char)などはこの寿命の対象に往々にしてならない。というのは、これらは Copyという属性をもっているからである。(死ぬ奴はDropという属性をもっている)


例えばこの変数の寿命というのは、


let x = String::from("Ohayo");

some_function(x);

println!("hi: {}", x);


などとすると、println!は実行されずコンパイルエラーになる。これは関数で呼び出されることで死神によって変数が「逝く」からである(そのかわり、some_function内には引き継いだxのコピーがいる)


ただし、これはあくまでも非スカラー(String, 配列, スカラー以外の型があるタプル、その他)のデータだけの特徴で、

スカラーのデータの場合


let x = 30;

some_function(x);

println!("hi: {}", x);

これはコンパイルエラーにならない。これは、スカラーデータのような単純データがdeep copyされたとことでshallow copyと大差ないことに起因する。


などなど、雑感をまとめてみた。

No comments:

Post a Comment