14

I would like to initialize a large object with a function. Currently I have:

fn initialize(mydata: &mut Vec<Vec<MyStruct>>) { /* ... */ }

I would prefer to have:

fn initialize() -> Vec<Vec<MyStruct>> { /* ... */ }

I've heard that C++ often implements return value optimization (RVO), if you are lucky and have a good compiler. Can we disable copying here and have it returned by a hidden pointer that is passed into the function? Is RVO part of the language or an optional optimization?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
stevenkucera
  • 3,855
  • 2
  • 18
  • 13
  • [This discussion](http://discuss.rust-lang.org/t/implementation-details/948/5) suggests that RVO triggers for anything larger than a pointer. But there's also [this open issue](https://github.com/rust-lang/rust/issues/18363) regarding NRVO. So I guess it depends on exactly how you implement `initialize`. – Michael Jan 08 '15 at 08:12

1 Answers1

22

Yes, by all means, you should write

fn initialize() -> Vec<Vec<MyStruct>> { ... }

(By the way, a Vec is not that large - it's only 3 pointer-sized integers)

Rust has RVO, and this is advertised in guides. You can see it yourself with this code:

#[inline(never)]
fn initialize() -> Vec<i32> {
    Vec::new()
}

fn main() {
    let v = initialize();
}

If you compile this program in release mode on the playground, outputting assembly, among everything else you will see this:

playground::initialize:
    movq    $4, (%rdi)
    xorps   %xmm0, %xmm0
    movups  %xmm0, 8(%rdi)
    retq

Vec::new() was inlined, but you can see the idea - the address for the fresh Vec instance is passed into the function in %rdi, and the function stores Vec fields directly into this memory, avoiding unnecessary copying through the stack. This is how it is called:

playground::main:
    subq    $24, %rsp
    movq    %rsp, %rdi
    callq   playground::initialize

You can see that eventually the Vec instance will be put directly into the stack memory.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Vladimir Matveev
  • 120,085
  • 34
  • 287
  • 296
  • Probably, but I find LLVM IR read much harder than the assembly for some reason :( – Vladimir Matveev Jan 08 '15 at 12:17
  • 2
    For this we just need the signature: `define internal fastcc void @initialize(%"struct.collections::vec::Vec<[i32]>[#3]"* noalias nocapture sret dereferenceable(24)) unnamed_addr #0`. LLVM IR is similar C for declarations, so that function returns `void` and takes a pointer (`*`) to a `struct.collections::vec::Vec<[i32]>`. (I used `#[no_mangle]` to make it clearer.) – huon Jan 08 '15 at 14:05
  • 1
    That might show passing a pointer, but if you look at the end of the LLVM IR, you might see a memcpy into that block of memory or similar. I have a piece of code I wrote that allocates a 256 int array to return and if you look at the bottom of the IR, you see the copy from one object to another. Looking at the generated ASM (both beta and nightly on play.r-l.org), you see the call to mempcy@PLT at the bottom of the function call. http://is.gd/u9GVx6 – JasonN Jul 08 '15 at 06:19