所有権
Rustの変数は束縛しているデータに対して以下のいずれかの権限を持っています。Rustがメモリ安全なのは、この権限によってメモリ破壊につながる操作を禁止しているからです。確保したメモリは開放を忘れてはいけないし、2度開放してもいけません。そのため、あるデータに対する所有者(変数)は常に一つであり、その変数がメモリを開放する責任を持っています。
メモリの開放 | 値の変更 | 読み出し | |
---|---|---|---|
所有(ミュータブル) | 〇 | 〇 | 〇 |
所有(イミュータブル) | 〇 | × | 〇 |
参照(ミュータブル) | × | 〇 | 〇 |
参照(イミュータブル) | × | × | 〇 |
変数のデータを別の変数に代入する場合、古い変数から新しい変数に所有権が移ります。これをムーブといいます。 | |||
参照を作成し、それを他の変数が束縛すると所有権を移すことなく値にアクセスできます。これを借用といいます。 |
fn main() {
let mut owner1 = 3; // owner1は3を所有しており、書き換えることもできる
let owner2 = 5; // owner2は5を所有している
println!("owner1 = {}, owner2 = {}", owner1, owner2);
owner1 = 8; // 書き換え可能
println!("owner1 = {}, owner2 = {}", owner1, owner2);
// borrowerはowner1のデータを借用する
// 可変な参照は1つしか作れない
let borrower = &mut owner1;
println!("borrower = {}", borrower);
*borrower = 15; // 可変なので、借用しているデータは書き換えることが出来る 書き換えには*による参照外しが必要
println!("borrower = {}", borrower);
// 不変な参照はいくつあっても良い
// 複数の変数がowner2のデータを借用する
let borrower2 = &owner2;
let borrower3 = &owner2;
let borrower4 = &owner2;
let borrower5 = &owner2;
println!(
"borrower2 = {}, borrower3 = {}, borrower4= {}, borrower5 = {}",
borrower2, borrower3, borrower4, borrower5
);
}
関数における所有権
引数に変数を渡すと所有権は引数に移ります。そして、関数のスコープを外れるときにメモリが解放されます。要するに、関数の引数に所有権を渡したデータは開放されます。
メモリを開放したくない場合は参照を渡します。
// この関数はdataの所有権を受け取り、関数の終了時にメモリを開放する。
fn take_ownership_and_delete(mut data:String){
data.push_str(":データ1の所有権を得ました");
println!("{}",data);
} // ここでdataのメモリは開放される
// この関数はdataを借用し、内容を変更する。
fn write_while_borrowing(data:&mut String){
println!("{}",data);
data.push_str("借用しました"); // 変更可能
} // dataは借用しているだけなのでここでメモリを開放しない。
// この関数はdataを借用する。
fn only_read_while_borrowing(data:&String){
println!("{}",data);
// data.push_str("参照しました"); //dataが持っているのは不変な参照なので変更できない。
} // dataは借用しているだけなのでここでメモリを開放しない。
fn main() {
let data_main1 = String::from("データ1");
take_ownership_and_delete(data_main1); // 所有権は引数に移動する
// println!("{}", data_main1); // data_main1所有権を失ったので使えない
let mut data_main2 = String::from("データ2");
write_while_borrowing(&mut data_main2); // 借用中に値は変更されるが所有権は渡さない
println!("{}", data_main2); // data_main2は所有権を持っているのでOK
let data_main3 = String::from("データ3");
only_read_while_borrowing(&data_main3); // 所有権の移動も値の変更もない
println!("{}", data_main3); // data_main3は所有権を持っているのでOK
}
Copyトレイト
Copyトレイトを実装した型は動的なメモリを確保しておらず、shallow copyとdeep copyの違いがありません。そのため代入や関数呼び出しではデータがコピーされ、所有権の移動は起きません。
余談
借用と参照は制限付きポインタって感じですね。新しい概念というより、コーディング規約みたいなものかもしれません。メモリはプログラマが管理しています。コンパイラはミスを指摘するだけです。結局のところ、ポインタでも(注意すれば)メモリ破壊を起こさずコーディングできる能力が求められる気がします。
サンプルコードを書いていると、自分が思っていたより情報量が多いことに驚きました。コードが少ないので単純な処理に見えますが、メモリ管理のロジックも含まれており、見た目よりも複雑な処理です。要するに見た目からはメモリを管理していることが分かりずらく、Rustの難しさはこの辺からきているような気がします。