ムーブセマンティクス
代入を行うと、変数間で_所有権_が移動します:
// 著作権 2023 Google LLC // SPDX-License-Identifier: Apache-2.0 fn main() { let s1 = String::from("Hello!"); let s2 = s1; dbg!(s2); // dbg!(s1); }
s1からs2への代入によって所有権が移動します。s1がスコープを抜けても、何も起こりません。何も所有していないためです。s2がスコープを抜けると、文字列データは解放されます。
s2 へムーブする前:
s2 へムーブした後:
関数に値を渡すと、その値は関数 パラメータに代入されます。これにより所有権が移動します:
// 著作権 2023 Google LLC // SPDX-License-Identifier: Apache-2.0 fn say_hello(name: String) { println!("Hello {name}") } fn main() { let name = String::from("Alice"); say_hello(name); // say_hello(name); }
-
これは C++ のデフォルトとは逆であることにも触れてください。C++ では
std::moveを使わない限り値渡しでコピーされます(そしてムーブコンストラクタが定義されている場合です!)。 -
移動するのは所有権だけです。データそのものを操作するためのマシンコードが 生成されるかどうかは最適化の問題であり、そのようなコピーは 積極的に最適化で取り除かれます。
-
単純な値(整数など)には
Copyを付けられます(後のスライドを参照)。 -
Rust では、クローンは明示的です(
cloneを使います)。
say_hello の例では:
- 最初の
say_hello呼び出しで、mainはnameの所有権を手放します。 その後、mainの中ではnameをもう使えません。 nameのために確保されたヒープメモリは、say_hello関数の終わりで 解放されます。mainは、nameを参照(&name)として渡し、かつsay_helloが パラメータとして参照を受け取るなら、所有権を保持できます。- あるいは、
mainは最初の呼び出しでnameのクローン (name.clone())を渡すこともできます。 - Rust は、ムーブセマンティクスをデフォルトにし、プログラマにクローンを 明示させることで、C++ よりも意図せずコピーを作りにくくしています。
さらに詳しく
現代の C++ における防御的コピー
現代の C++ では、これは別の方法で解決します:
std::string s1 = "Cpp";
std::string s2 = s1; // s1 のデータを複製する。
s1のヒープデータは複製され、s2はそれ自身の独立したコピーを持ちます。s1とs2がスコープを抜けると、それぞれが自分のメモリを解放します。
コピー代入の前:
コピー代入の後:
要点:
-
C++ は Rust とは少し異なる選択をしています。
=はデータをコピーするため、 文字列データはクローンされなければなりません。そうでないと、どちらかの 文字列がスコープを抜けたときに二重解放が起きてしまいます。 -
C++ には
std::moveもあり、これは値からムーブしてよいことを示すために 使われます。例がs2 = std::move(s1)だった場合、ヒープ割り当ては 発生しません。ムーブ後のs1は、有効ではあるものの未規定の状態に なります。Rust とは異なり、プログラマはs1を引き続き使用できます。 -
Rust と異なり、C++ の
=は、コピーまたはムーブされる型によって決まる 任意のコードを実行できます。