| Listing 4-1: 変数と、それがvalidなscope |
{ // s はvalid(有効)ではない
let s = "hello"; // s は valid
// s を使う // s は valid
} // scope を抜けたので sはもはや valid ではない。
o
|
s はscopeに入ると valid (有効)になる。s は valid なまま既に3章で紹介したデータ型はどれも既知のサイズであり、スタックの上に保存され、 scopeを抜けると捨てられるものであった。 ownership を理解するにはもう少し複雑なデータ型が必要になる。 異なるスコープで同じ値を使う必要がある場合は、新しい独立したインスタンスがコピーによって生成される。
しかしここでは、ヒープに保存されるデータを扱って、Rust がいかにしてその値が消去される時を知るかを見ていく。 String型がよい例となることだろう。
<今まで見てきた String literal は immutable (変更付加) である。
ユーザ入力を保存するのには String 型がある。 String は mutable (変更可)できる。 String 型は heap にデータを割り当てる。 String 型のtextの量はコンパイル時には不明である。
| String型変数の例 |
let mut s = String::from("hello");
s.push_str(", world"); // push_str() はStringにリテラルを追加する
println!("{s}""); // this will begin
|
String literal の場合は、コンパイル時にサイズが確定しているのでテクストが最終の実行可能ファイルに 直接ハードコーディングされる。これがString literal が高速で効率的な理由である。
String型の場合は、mutable (可変)で増加可能なテキストをサポートするために、 コンパイル時には未知の量のメモリをヒープに確保する必要がある。すなわち
String::from を呼び出せば実現できる。
2番目の項目は異なる。ガベージコレクタを備えた言語では、使われなくなったメモリはガベージコレクタが処理するので、 プログラマが考慮する必要はない。 ガベージコレクタを持たない言語では、使われなくなったメモリを開放するのはプログラマの責任となる。 これを適切に行うことはプログラマにとって大変難しい仕事である。
Rust では異なる道を進んでいる。メモリを所有している変数がそのスコープを抜けたらメモリは自動的に返還される
(drop関数による)。
スコープの例である Listing 4-1 を、 String literal の代わりに Srring を使って記述した版は次のようになる。
| String型変数とスコープの例 |
{
let s = String::from("hello"); // s is valid from th e point forward
// s を使う // s is valid
} // s is no longer valid
|
ヒープ上に確保されたデータを複数の変数が参照していると複雑な場面が起こり得る。 そのような場面について以下で検討していく。
Variables and Data Interacting with Move (moveによる変数とデータの相互作用)
| Listing 4-2: 変数x,yに整数値を代入する |
let x = 5;
let y = x;
|
整数は固定のサイズを持つ単純な値なので、2つの'5'という整数値はスタック上に確保されている。
| Listing 4-2b: 変数s1, s2 に文字列を代入する |
let s1 = String::from("hello");
let s2 = s1;
|
変数 s1, s2 の値は5個の文字からなる文字列であり、これはヒープ上に取られて共有されている。 変数 s1, s2 が scope を抜けたらそれぞれがデータを drop すると、2重開放エラーとなってしまう。
メモリ安全性を確保するために、let s2=s1; 行の後は s1 はもはや valid (有効)ではないと考える。
そのため s1 のscope外に移動したときには何も開放されない。
s2 だけが有効なので、スコープを抜けたら s2 だけがメモリを解放する。
| Listing 4-2c:所有権が移動するプログラム |
fn main() {
let s1 = String::from("hello");
let s2 = s1;
println!("{s1}, world");
}
|
| Listing 4-2c:メモリ安全性によるエラー |
$ rustc listing4_2c.rs
error[E0382]: borrow of moved value: `s1`
--> listing4_2c.rs:5:16
|
2 | let s1 = String::from("hello");
| -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 | let s2 = s1;
| -- value moved here
4 |
5 | println!("{s1}, world");
| ^^ value borrowed here after move
|
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider cloning the value if the performance cost is acceptable
|
3 | let s2 = s1.clone();
| ++++++++
warning: unused variable: `s2`
--> listing4_2c.rs:3:9
|
3 | let s2 = s1;
| ^^ help: if this is intentional, prefix it with an underscore: `_s2`
|
= note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default
error: aborting due to 1 previous error; 1 warning emitted
For more information about this error, try `rustc --explain E0382`.
|
invalidated reference (有効ではなくなった参照) を使ったためエラーとなる。
Rust では、自動的にデータの deep copy が行われることは絶対にないので、自動コピーは効率的に行われる。
Scope and Assignment
この逆は、scoping (範囲), ownership (所有権), drop() 関数によって解放されるメモリの関係にもあてはまる。 存在する変数に全く新しい値を代入したとき、Rust は drop() 関数を呼び出して元のデータを直ちに解放する。
| Listing 4-2d: 変数sに新しい文字列を代入する |
fn main() {
let mut s = String::from("hello");
s = String::from("ahoy");
println!("{s}, world");
}
|
Variables and Data Interactiong with Clone (複製による変数とデータの相互作用)
String型のスタック上のデータだけではなく、ヒープ上のデータも deep copy したい場合は、共通のメソッド
clone
を使う。
| clone による変数とデータの相互作用 |
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {s1}, s2 = {s2}");
|
clone関数の呼び出しがなくても正しく動作することに注意が
必要である。代入によってデータがそのままコピーされるからである。
| Listing 4-2: 変数x,yに整数値を代入する |
let x = 5;
let y = x;
|
Rust は Copy trait (トレイト、特性) とよばれる特別な annotation があり、
integer のようなスタックに配置されるデータ型に対して宣言できる。
データ型が Copy trait に適合していれば、代入後も古い変数が使用可能である。
データ型がその一部でも Drop trait を実装している場合は CopyM trait は宣言できない。
型の値が scope を出たときに何か特別なことをする必要がある場合は Code trait を宣言すると
コンパイル時にエラーがでる。
Ownership and Function (所有権と関数)
関数に値を渡す機構は、変数に値を代入する機構と似ている。 関数に値を渡すと、代入と同じようにmove や copy を行う。
| Listing 4-3: ownershipとscopeが注釈された関数 |
fn main() {
let s = String::from("hello"); // s が scope に入る
takes_ownership(s); // s の値が関数に move する
// ... もはや valid ではない
let x = 5; // x が scope に入る
makes_copy(x); // i32 は Copy trait を実装しているので、
// x は関数へ move されることはなく、
// これ以降で x を使っても大丈夫である。
} // x が scope からはずれる。sに関しては値が move されているので特別なことは何も起きない。
fn takes_ownership(som_string: String) { // some_string が scope に入る
println!("{some_string}");
} // some_string が scope からはずれるので, drop() が呼ばれて、メモリが解放される。
fn makes_copy(some_integer: i32) { // some_integer が scope に入る
println!("{some_integer}");
} // some_integer が scope からはずれる
|
Return Values and Scope (返り値とスコープ)
| Listing 4-4: 返り値の所有権を渡す |
fn main() {
}
|