mox692 のブログ

妄想の書き留め場所.

(自戒) Vecの不用意なindexアクセスでcloneを呼ばないようにしようね

どうも、最近RustのCloneの使用を極力減らしていきたい症候群に駆られてるmox692です.
内容はタイトルのままです.

Rustを触り始めて間もない頃はVecに対して安易に↓のようなコードを書きがちじゃないでしょうか.

#[derive(Clone, Copy)]  
struct A{}

fn main() {
    let a1 = A{};
    let a2 = A{};
    let v = vec![a1,a2];

    let b1 = v[0];
    let b2 = v[1];
}

Aの変数をVecに詰めて、それに対してindexアクセスしています. 他のプログラミング言語でもよく見るような構文ですね.

ここで一回、struct A{} の #[derive(Clone, Copy)]を消してしまいましょう. するとどうなるでしょうか

cannot move out of index of `Vec<A>`
 --> src/main.rs:9:14
  |
9 |     let b1 = v[0];
  |              ^^^^
  |              |
  |              move occurs because value has type `A`, which does not implement the `Copy` trait
  |              help: consider borrowing here: `&v[0]`

let b1 = v[0]; の行で怒られています.
もうお察しの方もいると思いますが、そうです. この v[0]; のようなindexアクセスを記述した場合、デフォルトではVecの要素がCopyされるようになっているのです. *1

その証拠にこのようなコードを動かしてみます.

#[derive(Clone, Copy)]
struct A{}
fn main() {
    let a1 = A{};
    let a2 = A{};
    println!("a1: {:p}", &a1);
    println!("a2: {:p}", &a2);
    let v = vec![a1,a2];

    let b1 = v[0];
    let b2 = v[1];
    println!("b1: {:p}", &b1);
    println!("b2: {:p}", &b2);
}

結果は

a1: 0x7ffcba70c490
a2: 0x7ffcba70c498
b1: 0x7ffcba70c548
b2: 0x7ffcba70c550

のようになり、b1, b1は元のa1, a2とは別のメモリ領域にある別の変数になっていることがわかると思います.

[T].get(...) の使用

cloneしても問題なければcloneしても大丈夫だと思いますが、取得したdataを変更しないことがわかってるのであればslice の get() を使用した方が 行儀がいいです.

moshg.github.io

まとめ

  • 他のプログラミング言語でよくあるような v[i] のようなindexアクセスによる代入は、コピーを発生させる.
  • コピーを発生させたくないのであれば、sliceのget methodを用いて参照を得た方が行儀がいい
  • 日々のコードで所有権がどのように移り変わっていくのかをもっと観察すべし (自戒!!!)

*1:インデックスアクセスでは参照を返すべきだ!みたいにいう人もいるかもしれませんし、実際そういうデザインの選択肢もありだったのかもしれません. ただそれだとさすがに不便 or 不自然だとRustチームは判断したんでしょうかねどうなんだろう

/* -----codeの行番号----- */