0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rustの所有権や計算速度に関するメモ

Last updated at Posted at 2025-04-19

Rustの所有権

Rustはc++などとは異なり、あるオブジェクトの所有者はただ一つのみ許される。

例えばこう言うことができない。

int* x = new int[5];
// xに代入などの処理

// yに付け替え
int* y = x;

// この後何やかんや処理。

これだとx,y両方が配列の所有者になってしまって、xからもyからも解放ができてしまう。Rustはこれを許さない。

c++だとunique_ptrが所有権を限定するような機能を持つが、get()とかで他の関数に渡したら、何かあっても実行時エラーになる。Rustはコンパイルの時に怒られるため、より厳格なんだと思う。

よくつまづく例

変更可能な参照

cppだと以下は実行可能。

cpp
double original = 0.0;
double *reference = &original;
original += 1.0;
*reference += 1.0;

Rustでは同じようなものを書いてもコンパイル前に怒られる。

rust
let mut original = 0.0;
let reference = &mut original;
original += 1.0;
*reference += 1.0;

3行目のoriginal += 1.0;がだめ。これは値を借用している最中は元の値を使った操作ができないために起きる。
referenceoriginalを変更可能な参照として作られる。Rustではreferenceの寿命が尽きるまでoriginalを使えない。なので、以下のようにすると実行できる。

rust
let mut original = 0.0;
let reference = &mut original;
*reference += 1.0;
original += 1.0;

3,4行目を反転させた。こうすると、この処理以降referenceが登場しなければ、3行目の処理でreferenceの役割は終わって、借用していた所有権をoriginalに返却する。わかりやすく書けばこう言うこと。

rust
let mut original = 0.0;
{
    let reference = &mut original;
    *reference += 1.0;
}
original += 1.0;

ライフタイム

cppだとこうしたいことがよくある。(例: 木構造など)

cpp
class Node{
  public:
    Node* child;
}

こうすることであるNodeから別のNodeに移っていくことができるが、Rustはこれを簡単には許さない。
この構造の場合、以下のような実装を行う可能性がある。

cpp
Node node1;
Node node2;

node1.child = &node2;

すごくわかりやすくていいのだが、この場合node2の所有者は誰か疑問になってしまう。node1.childからも操作できてしまうし、もちろんnode2を直接操作することもできる。これはRustが許すはずがない。
また、スコープの関係でnode1よりも先にnode2が消えてしまうかもしれない。こう言うことをRustは厳しく取り締まっている。

ここで出てくるのがライフスパンで、寿命のようなもの。

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文を使う方法がある。アルゴリズム的に使いづらいこともあるが、基本的にはどれが速いのか確かめてみた。

一応それぞれ使いやすいシーンが違うと思うので、できるだけ条件を合わせるため、計測範囲はベクトルの作成から完成までとする。

iter().map()
// 元の乱数列作成
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();
iter_mut().for_each()
// 元の乱数列作成(省略)
// 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
// 元の乱数列作成(省略)
// 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が平均的にみて遅そうだが、要素数ごとの時間変化はどれも同じくらいで、特にどれを使ったからといって速さに差は無さそう。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?