背景
最近趣味でRustの勉強をしているが、変数や関数の取り扱いでC++と似ているようで異なる点があり少し混乱した。
これらをC++と比較することで理解がしやすくならないかと思ったのでまとめてみた。
なお、意味的に似ているもの同士を比較したので正確性は犠牲になっているはず。
変数の比較
Rustでは変数はデフォルトでC++におけるconstのような振る舞いをする、つまり値を後から変えることができない。
- Rustにもconstはあるが、通常の変数とは若干使い方が異なる
値を変えたい場合は宣言時にmutという修飾子をつける必要がある。
また変数に型を宣言しなくても、 c++のautoのように代入される値によって型を推定してくれる、
fn main() {
let x: i32 = 1; // C++, const int x = 1;
let mut y = 1; // C++, auto y = 1; y += 1;
y += 1;
println!("{}, {}",x, y);
}
関数の比較
ムーブセマンティクス
ムーブセマンティクスとはある変数が持っているデータをそのまま別の変数に移動させるような操作を指す
C++の場合、
- ムーブを対応させたい型に右辺値参照型を引数にとるコンストラクタや代入演算子を定義し、その中でデータの移動を実装する
- 呼び出し元はstd::move()を変数につけることで右辺値参照型にキャストすることができる
- このキャストした変数を渡した先がムーブセマンティクスを実装していれば、ムーブが行われる
つまり、std::move()した変数をある関数に渡しても、渡した先がムーブセマンティクスを実装していなければムーブは起きない。
ムーブされた変数は今後値が変わったり削除されたりする可能性がある、と言っているだけなので、ムーブした後でもその変数にアクセスできる(推奨はされないが)
C++でコンストラクタや代入演算子以外で右辺値参照を引数にとる関数を書くことはあまりないと思うが、以下のように関数の中でムーブさせるような関数を書くこともできる。
#include <iostream>
#include <string>
using namespace std;
void print_string(string&& s){
string y = "";
y = move(s);
y.insert(y.size()," pushed");
cout << y << endl; // Hello pushed
}
int main(){
string x = "Hello";
print_string(move(x));
cout << x << endl; // C++ allow to access moved variable but valid but unspecified state
}
Rustの場合、関数に値を渡すとムーブが起きる(Rustでは所有権が移ったという)。
- ただしi32のような一部のプリミティブ型ではムーブではなくコピーされる
またムーブされた変数にはアクセスすることはできないので、その点でC++よりも安全だといえる。
fn print_string(s: String) {
println!("{}", s); // Hello
}
fn main() {
let x = String::from("Hello");
print_string(x);
// println!("{}", x); // NG in Rust
}
const 参照渡し
C++では変数の値は変更しないがコピーのコストが大きいときに例えれば参照渡しにconstをつける
#include <iostream>
#include <string>
using namespace std;
void print_string(const string& s){
cout << s << endl;
}
int main(){
string x = "Hello";
print_string(x);
cout << x << endl;
}
Rustでは単に参照を渡せば関数に渡した後も変数にアクセスできる(Rustでは借用という)
fn print_string(s: &String) {
println!("{}", s);
}
fn main() {
let x = String::from("hello");
print_string(&x);
println!("{}", x); // OK
}
参照渡し
C++では変数を渡した先の関数で変更したい場合は(constをつけない)参照渡しを使う。
#include <iostream>
#include <string>
using namespace std;
void print_string(string& s){
s.insert(s.size()," pushed");
cout << s << endl;
}
int main(){
string x = "Hello";
print_string(x);
cout << x << endl;
}
Rustの場合、前述の通り単に参照を渡しただけでは値を変更することができない。
渡された先で値を変更したい場合、渡す変数と仮引数の型にmutをつけて可変参照にする必要がある。
fn print_string(s: &mut String) {
s.push_str(" pushed");
println!("{}", s);
}
fn main() {
let mut x = String::from("hello");
print_string(&mut x);
println!("{}", x);
}
値渡し
C++で関数に変数を値渡しをすると渡された先で変数はコピーされる
#include <iostream>
#include <string>
using namespace std;
void print_string(string s){
s.insert(s.size()," pushed");
cout << s << endl; // Hello pushed
}
int main(){
string x = "Hello";
print_string(x);
cout << x << endl; // Hello
}
しかし前述のようにRustでは関数に変数を渡すとムーブされるので、(基本的には)後でその変数にアクセスすることはできない
そのためclone()を使って、変数のコピーを関数に渡してやる必要がある
fn print_string(mut s: String) {
s.push_str(" pushed");
println!("{}", s); // Hello pushed
}
fn main() {
let x = String::from("Hello");
print_string(x.clone());
println!("{}", x); // Hello
}
ただしi32のような一部のプリミティブ型は自動的にコピーされるのでこのようなことをする必要なない
まとめ
変数の比較
| C++ | Rust |
|---|---|
| const int x; | let x: i32; |
| int x; | let mut x; |
| const auto x = 1; | let x = 1; |
関数の比較
| 引数の渡し方 | C++ | Rust |
|---|---|---|
| ムーブ | void function(data&& x) | fn function(x: data) |
| const 参照渡し | void function(const data& x) | fn function (x: &data) |
| 参照渡し | void function(data& x) | fn function (x: &mut data)//呼び出し側でmut |
| 値渡し | void function(data x) | fn function(mut x: data) //呼び出し側でclone() |