Rustの所有権
Rustはc++などとは異なり、あるオブジェクトの所有者はただ一つのみ許される。
例えばこう言うことができない。
int* x = new int[5];
// xに代入などの処理
// yに付け替え
int* y = x;
// この後何やかんや処理。
これだとx,y両方が配列の所有者になってしまって、xからもyからも解放ができてしまう。Rustはこれを許さない。
c++だとunique_ptrが所有権を限定するような機能を持つが、get()とかで他の関数に渡したら、何かあっても実行時エラーになる。Rustはコンパイルの時に怒られるため、より厳格なんだと思う。
よくつまづく例
変更可能な参照
cppだと以下は実行可能。
double original = 0.0;
double *reference = &original;
original += 1.0;
*reference += 1.0;
Rustでは同じようなものを書いてもコンパイル前に怒られる。
let mut original = 0.0;
let reference = &mut original;
original += 1.0;
*reference += 1.0;
3行目のoriginal += 1.0;
がだめ。これは値を借用している最中は元の値を使った操作ができないために起きる。
reference
はoriginal
を変更可能な参照として作られる。Rustではreference
の寿命が尽きるまでoriginal
を使えない。なので、以下のようにすると実行できる。
let mut original = 0.0;
let reference = &mut original;
*reference += 1.0;
original += 1.0;
3,4行目を反転させた。こうすると、この処理以降reference
が登場しなければ、3行目の処理でreference
の役割は終わって、借用していた所有権をoriginal
に返却する。わかりやすく書けばこう言うこと。
let mut original = 0.0;
{
let reference = &mut original;
*reference += 1.0;
}
original += 1.0;
ライフタイム
cppだとこうしたいことがよくある。(例: 木構造など)
class Node{
public:
Node* child;
}
こうすることであるNodeから別のNodeに移っていくことができるが、Rustはこれを簡単には許さない。
この構造の場合、以下のような実装を行う可能性がある。
Node node1;
Node node2;
node1.child = &node2;
すごくわかりやすくていいのだが、この場合node2の所有者は誰か疑問になってしまう。node1.childからも操作できてしまうし、もちろんnode2を直接操作することもできる。これはRustが許すはずがない。
また、スコープの関係でnode1よりも先にnode2が消えてしまうかもしれない。こう言うことをRustは厳しく取り締まっている。
ここで出てくるのがライフスパンで、寿命のようなもの。
struct Node<'a> {
child: &'a mut Node<'a>,
}
<'a>
がライフスパンで、そもそもどの変数にもライフスパンはあるのだが、Node<'a>と書くことで、明示的にライフスパンに名前をつけて扱えるようになる。
この例だと、struct Node<'a>
でNodeのライフスパンを'aということにしている。そのメンバ定義ではchild: &'a mut Node<'a>
によって、childは親と同じ'a
というライフスパンを持つNode型で、親と同じタイミングで解放される。そしてその型を同じく'a
のライフスパンを持つ参照を使ってchildに当てはめる。
iter().map()とiter_mut().for_each()とfor
あるベクトルの各要素に値を計算して入れる場合、v.iter().map().collect()とする方法、v.iter_mut().for_each()を使う方法、普通にfor文を使う方法がある。アルゴリズム的に使いづらいこともあるが、基本的にはどれが速いのか確かめてみた。
一応それぞれ使いやすいシーンが違うと思うので、できるだけ条件を合わせるため、計測範囲はベクトルの作成から完成までとする。
// 元の乱数列作成
let num = 100000;
let mut rng = rand::rng();
let rand_src = vec![0.0; num]
.iter()
.map(|_| rng.random::<f64>())
.collect::<Vec<f64>>();
// iter().map()
let mut dur: time::Instant = time::Instant::now();
#[allow(unused)]
let rand_dst1: Vec<f64> = rand_src
.iter()
.map(|x: &f64| x + rng.random::<f64>())
.collect::<Vec<f64>>();
let dur_iter: time::Duration = dur.elapsed().clone();
// 元の乱数列作成(省略)
// for_each()
dur = time::Instant::now();
#[allow(unused)]
let mut rand_dst2: Vec<f64> = rand_src.clone();
rand_dst2.iter_mut().for_each(|x| *x += rng.random::<f64>());
let dur_fe: time::Duration = dur.elapsed().clone();
// 元の乱数列作成(省略)
// for
dur = time::Instant::now();
#[allow(unused)]
let mut rand_dst3: Vec<f64> = vec![0.0; num];
for x in rand_dst3.iter_mut() {
*x += rng.random::<f64>();
}
let dur_for: time::Duration = dur.elapsed().clone();
要素数を変えて10回ずつ平均をとった。
iter().map() [us] | for_each() [us] | for [us] | |
---|---|---|---|
10 | 1.084 | 1.669 | 1.456 |
100 | 7.916 | 7.251 | 8.915 |
1000 | 71.294 | 66.125 | 75.750 |
10000 | 765.789 | 667.123 | 745.458 |
100000 | 7353.084 | 7104.751 | 8084.876 |
forが平均的にみて遅そうだが、要素数ごとの時間変化はどれも同じくらいで、特にどれを使ったからといって速さに差は無さそう。