なんとなくRustが気になって触ってたら借用(borrow) とか所有権(ownership) とか参照(references) とかムーブ(move) とか可変(mutable) とか不変(immutable) とか、参照はともかく他の言葉はなじみがない。メモを残すしかない。
可変と不変
Rustで変数宣言をするときはletを使い宣言する。型を指定しなくても大丈夫。型指定する場合には変数名に続けて : 型 を指定する。
//変数宣言
let val = 1;
//型指定して変数宣言
let val: i32 = 1;
C言語やJava言語のように型を先に指定しないことや、型名に驚くが気にしない。
不変 immutable
これに驚いた。変数は基本的に不変。変数なのに!?
どうやらRustでは不変が基本。これは定数(constant) とは異なる。
//変数宣言
let val = 1;
//...
val = 2; // <-- これはNG:valは不変であり変化しない。
もし、valの値を変えたいなら以下のように再宣言し直すことで解消出来る。
//変数宣言
let val = 1;
//...
let val = 2; // <-- これはOK
同じ名前の変数を宣言してはいけない と言われ、教えてきた身からすると不思議な感覚だった。
可変 mutable
可変な変数を宣言するにはmutを変数宣言時に使う必要がある。
//可変な変数宣言
let mut val = 1;
val = 2; // <-- これはOK:valは可変な変数であり変化する。
所有権とムーブ
どうやらRustの変数には所有権という考え方があるらしい。
所有権 ownership
まず、Rustの変数は値に対する所有権を持っている。
let val1 = 1;
上記のプログラムでは変数val1は値「1」に対する所有権を持つことになる。
let val1 = 1;
let val2 = val1;
上記プログラムでは変数val2はval1の値「1」が複製され、新たな「1」に対して所有権を持つことになる。値自体は同じ「1」でも、メモリ上は異なった存在。
基本データ型(primitives)の値は、他の言語と大きく異なり点もなく悩む必要が無い。
しかし、それ以外の型では少々複雑な動きを見せる。
例えばString型のデータを扱う場合が例に挙げられる。
以下記述によってString型のデータを生成する事が出来る。
//変数string_value1にString型の「hello」が格納れる。
let string_value1 = String::from("hello");
println!("string_value1 is {}", string_value1);
この記述に一行追加すると、エラーになる。
//変数string_value1にString型の「hello」が格納れる。
let string_value1 = String::from("hello");
let string_value2 = string_value1; //<-- これを追加
println!("string_value1 is {}", string_value1);
// > error[E0382]: borrow of moved value: `string_value1`
一見、問題無く動きそうな記述だがエラーになる。
これは所有権の考え方が絡んでいるため。let string_value2 = string_value1;
という記述は、所有権の移動となる。
要するにstring_value1が所有していたString型のデータに対する権利を、string_value2に渡してしまうことになり、string_value1は所有権を持たない状態になる。その為、エラーとなってしまう。この所有権の移動をムーブと呼ぶ。
Stringと&str
ここは一旦メモ
- &strは基本型。不変。長さ固定。
借用 borrow
所有権はメモリをより安全に効率的に扱う事が出来る仕組み。所有権をなんとなくイメージできれば借用は大した話ではなかった。言葉の通り一時的に所有権を渡して、処理が終わったら返してもらうという様な感じで使いたいだけのこと。(実際には所有権は渡さない)
これは関数の話で説明されていて、引数として変数を与えたときに重要な考え方。
以下のプログラムは引数を使った関数の例。
fn main(){
let mut string_value = String::from("hello");
string_print(string_value); //<--ここで関数の呼びだし。引数を与えている。
}
fn string_print( val: String){
println!("val is {}", val);
}
//output >>val is hello
このプログラムは問題なく動くが、次のように改変すると動かなくなる。
fn main(){
let mut string_value = String::from("hello");
string_print(string_value);
string_print(string_value); //<--ここで同じ関数を同じ引数で呼びだし。エラーになる。
}
fn string_print( val: String){
println!("val is {}", val);
}
//output >>error[E0382]: use of moved value: `string_value`
実際はもっと詳細な情報が出るがコンパイルするとerror[E0382]: use of moved value: string_value
になる。要するに既にムーブしたstring_valueを使っているという指摘。引数として変数を指定しても所有権のムーブは発生するということ。関数の引数に変数を渡した場合、所有権が移ってしまうため続けて利用する事が出来ないということになる。これを解決するには戻り値を利用する方法がある。
fn main(){
let mut string_value = String::from("hello");
string_value = string_print(string_value);
string_value = string_print(string_value);
}
fn string_print( val: String) -> String{
println!("val is {}", val);
return val; // <-- 引数で受けとった値を、呼びだし元に返す
}
引数で渡した値を関数から返してもらえば解決する。だが、手間。ここで登場するのが借用。
一時的に所有権ごと貸し出し、利用後に返してもらうイメージ。以下のような記述になる。
fn main(){
let mut string_value = String::from("hello");
string_print(&string_value); // <-- 引数に「&」が付加されている
string_print(&string_value);
}
fn string_print( val: &String){ // <-- 引数に「&」が付加されている
println!("val is {}", val);
}
要は参照のこと。所有権を渡してしまうと問題があるのでオブジェクトに対する参照を引数として渡す。所有権はstring_valueが保持し続けている。関数の引数に所有権の代わりに参照を渡すことを借用と呼んでいる。
ただし、このままでは所有権は持っていないため関数内で値に変更を加えることは出来ない。可変可能な参照は次のように記述する。
fn main(){
let mut string_value = String::from("hello");
add_world(&mut string_value); // <-- 引数に「&mut」が付加されている。可変可能な参照を渡す。
string_print(&string_value);
}
fn add_world( val: &mut String){ // <-- 引数に「&mut」が付加されている
val.push_str(" world");
}
fn string_print( val: &String){
println!("val is {}", val);
}
最後に
RustはGC(ガーベジコレクション)を持たない言語でありながら、メモリを安全に扱える言語らしい。確かにC言語を勉強していたときのようにメモリの確保とか解放とか意識せずに書ける。逆にJava等のようにGCに任せて勝手に解放されるのも良いが、Rustはいつメモリが解放されるのか(所有権を保持した変数のスコープが終了した時)が明確。安全に使えてる気がしてきた。いずれにしても、Rustは所有権って考えを整理しておかないとダメだと思った。
なんとなく見かけて触り始めたRustです。このメモにも間違いはあるかもしれませんが少しでも参考になればと思います。