Rustで値のmoveが起こる場合
Rust v.0.11pre時点での情報です。
Rustにおけるムーブセマンティクス
だいたいC++のそれと同じものと理解してるんだけど、おさらいとして。
Rustの言語セマンティクスの一つに、メモリの所有権(Ownership)というものがある。これは「このメモリ領域を自身のもの所有しているものは常に一つ(ユニーク)になる」というもので、これが保証されると、特定のメモリの領域は、所有する変数ひいては所有するタスクが決定されるようになる。原則として、各タスクごとの単位でしか、その所有権の貸し借り(borrowing)ができないため、タスクAから他の非同期に動作するタスクBの所有するメモリ領域の変更は不可能になる。タスクAからタスクBに向けてデータを送信した場合、データの所有権ごと送信されるので、結果としてデータ競合が発生しないことが保証される(もちろんArc<T>
とか使ったり、生ポインタを送信するみたいな抜け道はありますよ)。
このOwnershipの概念に基づいて、変数のmutability規則が決まっていたり、borrowingの説明がなされるようになる。タスク間でデータをやりとりできる型かどうかについては、Send
kindであるかどうかとかが絡んでくるんだけど、マニュアル読んだ方が速いのでリンクを貼るだけにとどめる。
もうひとつ、Rustではポインタに関しては、基本的にはshallow copyとなる。 なので、チュートリアルに載っている以下のようなコードでは、ポインタの複製にとどまる。
let xs = Cons(1, box Cons(2, box Cons(3, box Nil))); let ys = xs; // copies `Cons(u32, pointer)` shallowly
ここで問題発生。
ここでコピーされたポインタ値はowning pointerのもの。即ち、そこにはownershipが存在している。だが、ポインタ値のみがコピーされたことで、所有していると見なされるオブジェクトが2つに増えてしまった。これではOwnershipの説明と矛盾する。
Rustでは、この矛盾の解消のために、ownershipを移動するということを行う。さっきのコードの例で言えば、子孫となっているCons
オブジェクトの所有権は、ポインタのコピーと同時にxy
からys
に対して移動(ムーブ)される。そして、古い方の所有者であるxy
は無効なものとして、以後、使用することはできなくなる(コンパイルエラー!)。
以上が、RustにおけるOwnershipと、それに基づくムーブセマンティクスの簡単な説明になる。 これらにより、セマンティクスの面では安全性が保証され、処理系の最適化という文脈では余計なメモリコピーが発生しなくなるのでうれしくなる、というわけ。
ムーブが起こる場合
それでは、ムーブが発生する場合は具体的にどういうケースなのかということになるので、リファレンスマニュアルから、一通り抜き出してみた。
Rustにおける代入などの操作で、copyではなくmoveとなる条件は以下の通り:
- 型がコピーではなくムーブの対象と指定されている場合(代入など)
- 例としては、型が内部にowning pointer(
Box<T>
)を含む Drop
traitを実装している- つまり、Type kinds(型の種類)が
Drop
になる場合。尚、Drop
kindは、制約上Send+Drop
になる.(ムーブ対象の値を子孫方向に含む場合も同様)
- つまり、Type kinds(型の種類)が
- Defaultとして、デストラクタ(
Drop::drop()
)を実装している型、クロージャ環境、非ファーストクラスな型はコピー不可でありムーブする- vectorが固定長である(
[int, ..10]
のように表現できる)場合は、静的にサイズが決定できる為、ファーストクラスとなる - 一方、可変長である場合(
Vec<T>
または&[T]
を用いてのみ生成可能)、非ファーストクラスとなる。
- vectorが固定長である(
- 例としては、型が内部にowning pointer(
return
式を用いて値を返す場合- 関数で
return
を省略する場合には、最後の式が値を生み出す場合は、暗黙的にreturn
で返していると解釈されるので、return
を明示しようがしまいが関数の戻り値はmoveする - ブロック式の末尾の値が返る場合も同じ様子
- 関数で
- ジェネリック関数においては、ジェネリックな型が不明瞭(opaque)である場合、引数の関数内での挙動はmoveに限定される
- (
<T: Copy>
のように)型がCopy
kindであることが指定されていれば、copyされる
- (
- クロージャ型がプロシージャ(
proc
)である場合、参照する外部変数はムーブされる - タスク間でデータをやり取りする場合
- この場合、
Send
kindである必要がある
- この場合、