Rust で swap したらヒープを感じた
■この記事のポイント
- ヒープを使うような変数を 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