まえがき
この記事はRUSTチュートリアルをZerobillbank社内メンバー(有志)で勉強がてら、可能な限りわかりやすく内容を書き出す企画の一部です。
本項では、RUSTのキモとされる 所有権 の考え方について記したいと思います。
なお、初学者のため(略)という免責を打ちつつ間違いなどあればコメントで教えていただけますと一同嬉しく思います!
概要
RUSTでは変数を束縛するという考えがあるのは先述の通りですが、この 束縛する
という考えの元は定義した変数が所有権を持つことに由来します。
本項では、所有権の概念と考えかたについて記載し、説明します。
RUSTにおける所有権という概念
全章で説明した変数束縛は、束縛されているものの「所有権をもつ」という考えを持っており、
スコープ内でメモリが確保され、スコープを抜ければそのメモリは解放される特徴を持ちます。
fn foo() {
let v = vec![1, 2, 3];
}
上記の例で言うとこのような動作になります。
- v がスコープに入るとき、新しい Vec が作られる。
- さらにベクタは3つの要素のために ヒープ(メモリ) に空間を割り当てる。
- foo() の最後で
v
がスコープから外れるとき、Rustはvのベクタに関連するもの全てを取り除く。
ムーブセマンティック
RUSTの所有権は、メモリの確保、及び開放の他に リソースに対する束縛は一つである ということを保証します。
例えば以下のようなコードはエラーになります。
let v = vec![1, 2, 3];
let v2 = v;
println!("v[0] is: {}", v[0]); // -> Error!
上記の例では v で束縛していたvectorは、次の行でv2に所有権がうつっています。
そのため、3行目で v を呼び出そうとしても、v のメモリ空間は解放されているためエラーが発生します。
また、所有権の移り変わりは変数名の変更意外にも、 関数へ渡した際にも所有権は移ります。
fn take(v: Vec<i32>) {
}
let v = vec![1, 2, 3];
take(v);
println!("v[0] is: {}", v[0]); // -> Error!
RUSTでは、 所有権を何か別のものに転送(付け替え)たとき、「ムーブした」と言う みたいです。
Copy trait
Rustの所有権、それから所有権の移動について説明をしてきました。
続いて、変数の所有権をそのままに変数をコピーし参照を行うCopy traitについて説明します。
すでにコンパイラ側に実装されており自動で適用されてしまうため例としては不適切(わかりにくい)かもしれませんが、
以下のように i32
型はcopy traitがコンパイル時に自動で補完(実装)されるためエラーになりません。
let v = 1;
let v2 = v;
println!("v is: {}", v);
i32
だけではなく、プリミティブ値は全てcopy traitが自動実装されるため所有権の移行は適用されません。
所有権を返却する
例えば変数を受け取って、その変数を処理したあとに変数をそのまま返却する関数を実装したとします。
fn foo(v: Vec<i32>) -> Vec<i32> {
// vについての作業を行う
// 参照したvを返却
v
}
上記のように実装することで、変数の所有権をそのまま返却することが可能です。