🦀

Rust で swap したらヒープを感じた

2023/11/26に公開

■この記事のポイント

  • ヒープを使うような変数を swap すると、ポインタが交換される

■やりたいこと

サイズが大きい 2 つのデータを入れ替えるとき、その都度コピーが発生していたら残念ですよね。できれば実データは動かしたくないものです。

Rust には std::mem::swap という関数が存在するので、これがどのように動作するのか試してみたいと思います!

■Vec を swap してみる

ベクタを2つ持ってきて、swap してみましょう!

swap は vec1 と vec2 の中身を交換してくれますが、今回は中身ではなく各ベクタの持つ「ポインタ」がどのよう動くかに注目します。
以下では、as_ptr() を使ってポインタの変化を見ています。

let mut vec1 = vec![1; 10];
let mut vec2 = vec![2; 10];

println!("---swap前---");
println!("vec1: {:p}", vec1.as_ptr());
println!("vec2: {:p}", vec2.as_ptr());

swap(&mut vec1, &mut vec2);

println!("---swap後---");
println!("vec1: {:p}", vec1.as_ptr());
println!("vec2: {:p}", vec2.as_ptr());

出力例は以下の通りです。swap の前後でポインタが交換されていますね!

---swap前---
vec1: 0x143e06a80
vec2: 0x143e06ab0
---swap後---
vec1: 0x143e06ab0
vec2: 0x143e06a80

上の例から、Vec はヒープ上に確保した実データを移動することなく、実データを指し示すポインタを交換するだけで swap を実現させていることがわかります!

■Vec を swap してみる②

これまでは2つの Vec を使っていましたが、次は2次元のベクタ Vec<Vec<i32>> で考えてみましょう。

以下のコードは残念ながらコンパイルが通りません。vec の可変参照 (&mut) が同時に 2 つ存在するからです。

let mut vec: Vec<Vec<i32>> = vec![vec![0; 10]; 2];
swap(&mut vec[0], &mut vec[1]);
// コンパイルエラー: cannot borrow `vec` as mutable more than once at a time

この場合は、 vec.swap() を用いるとよいでしょう。

let mut vec: Vec<Vec<i32>> = vec![vec![0; 10]; 2];

println!("---swap前---");
println!("vec[0]: {:p}", vec[0].as_ptr());
println!("vec[1]: {:p}", vec[1].as_ptr());

vec.swap(0, 1);

println!("---swap後---");
println!("vec[0]: {:p}", vec[0].as_ptr());
println!("vec[1]: {:p}", vec[1].as_ptr());

出力例は以下の通りです。
先程の例と同様に、きちんとポインタが交換されていることがわかります。

---swap前---
vec[0]: 0x150606d50
vec[1]: 0x150606ae0
---swap後---
vec[0]: 0x150606ae0
vec[1]: 0x150606d50

■String を swap してみる

最後に、String も同様に as_ptr()で確認していきましょう!

let mut string1 = "aaa".to_string();
let mut string2 = "bbb".to_string();

println!("---swap前---");
println!("string1: {:p}, {}", string1.as_ptr(), string1);
println!("string2: {:p}, {}", string2.as_ptr(), string2);

swap(&mut string1, &mut string2);

println!("---swap後---");
println!("string1: {:p}, {}", string1.as_ptr(), string1);
println!("string2: {:p}, {}", string2.as_ptr(), string2);

出力例は以下の通りです。

---swap前---
string1: 0x12c606b80, aaa
string2: 0x12c606d80, bbb
---swap後---
string1: 0x12c606d80, bbb
string2: 0x12c606b80, aaa

やはりこちらもポインタが入れ替わっているようです。
String は内部的に Vec<u8> として保持されるので、Vec と同様の挙動になるのは説明がつきますね!

■まとめ

Rust の std::mem::swap を用いることで、ヒープに確保されたデータを動かすことなく、ポインタの交換によって変数を入れ替えることができました🦀

Discussion