関数からの戻り値のオーバーヘッドが気になったので調べてみた

モチベ

Rust では以下のようなコードはコンパイルでエラーになります。

fn func1() -> &'static String {
    &String::from("test")
}

fn main() {
    println!("{}", func1());
}

コンパイル結果

error[E0515]: cannot return reference to temporary value
 --> src\main.rs:2:5
  |
2 |     &String::from("test")
  |     ^--------------------
  |     ||
  |     |temporary value created here
  |     returns a reference to data owned by the current function

エラーの通り、関数内で作成した値の参照を返すことは基本的にできません。 なので、値を返したければ、値そのものを返すようにしないといけません。

fn func1() -> String {
    String::from("test")
}

fn main() {
    println!("{}", func1());
}

↓実行結果

test

ただ、値を返すとなるとコピーのオーバーヘッドが気になるところですよね。 ネットでも同じようなことを気にされている方がいらっしゃいました。

Function returning a String, does it copy the value? - help - The Rust Programming Language Forum

回答者の方は、「値返しの場合はCopyやcloneは発生せずMoveが発生するだけだから、そこまで気にするオーバーヘッドは発生しないから気にしなくていいよ」 という回答をされていますが、とはいえMoveが発生するということは新しくメモリを確保してそこに移動するってことなので、ある程度はパフォーマンスの低下が発生するのでは?と思ったのでした。

調査

簡単なタプルを作って、関数の戻り値のアドレスがどう変化するのかを見てみました。

#[derive(Debug)]
struct MyStruct(i32, Vec<i32>, i32);

fn dump(title: &str, x: &MyStruct) {
    println!("{}", title);
    println!("  x      : {:p}", &(x));
    println!("  x.0    : {:p}", &(x.0));
    println!("  x.1    : {:p}", &(x.1));
    println!("  x.1[0] : {:p}", &(x.1[0]));
    println!("  x.2    : {:p}", &(x.2));
    println!();
}

fn func1() -> MyStruct {
    let x = MyStruct(2, vec![1], 4);
    dump("in func1()", &x);
    x
}

fn main() {
    let mut x = MyStruct(1, vec![2], 3);

    dump("first x", &x);

    x = func1();

    dump("second x", &x);
}

結果↓↓↓

first x
  x      : 0x64f7bf278
  x.0    : 0x64f7bf498
  x.1    : 0x64f7bf480
  x.1[0] : 0x12a4c1591f0
  x.2    : 0x64f7bf49c

in func1()
  x      : 0x64f7bf218
  x.0    : 0x64f7bf4d0
  x.1    : 0x64f7bf4b8
  x.1[0] : 0x12a4c159210
  x.2    : 0x64f7bf4d4

second x
  x      : 0x64f7bf278
  x.0    : 0x64f7bf498
  x.1    : 0x64f7bf480
  x.1[0] : 0x12a4c159210
  x.2    : 0x64f7bf49c

結果から分かることは、

  • 関数から戻り値は別の場所にムーブされている
  • 構造体内のベクタ変数の実態はムーブされず、ベクタ変数への参照のみがムーブされている

推察

ここから察するに、『関数からの戻り値はシャローコピーのみが発生している』ということですかね。 ディープコピーじゃないからパフォーマンス的にもそんなに気にすることじゃなくて メモリ的にも優しいよって話なのかもですね。