Will Box::new() copy memory?

struct SomeStruct {
    // omitted
}

fn function1() -> Box<SomeStruct> {
    Box::new(SomeStruct{/*omitted*/})
}

fn function2() -> Box<SomeStruct> {
    let foo = SomeStruct{/*omitted*/};
    Box::new(foo)
}

I know Box allocates memory on heap. Two questions:

  1. Any difference between function1 and function2 as far as memory allocation is concerned?
  2. In function2, is foo first allocated on stack and then copy to heap by Box::new?
    Thanks!
3 Likes

No difference between the two in terms of allocation. And yeah, generally speaking the struct is copied to the heap when placed inside a Box. I say "generally" because the optimizer (compiler) may skip the stack allocation.

This is actually an issue when trying to box large arrays - the large array may overflow the stack before it's copied to the heap. There's some work to define placement new operators, and that should allow for allocating on the heap directly.

5 Likes

For first question, in your example, there is an implicit assignment in function1 (to store the value of SomeStruct somewhere so that Box::new could read it), so there is no difference even in unoptimized mode.

As for your second question, well, there is no reason to allocate it on stack, you don't provide a reference to that value to a non-inlined function before boxing, so with optimizations, likely no stack allocation will happen (it's optimizations, you can never be sure).

Both functions have identical output code, but just to show, I compiled the Rust code to assembly with commentary (modified to actually write something, as allocating 0-bytes is not too useful).

pub struct SomeStruct {
    value: i32,
    other_value: i32,
}

pub fn function1() -> Box<SomeStruct> {
    Box::new(SomeStruct {
                 value: 42,
                 other_value: 24,
             })
}

pub fn function2() -> Box<SomeStruct> {
    let foo = SomeStruct {
        value: 42,
        other_value: 24,
    };
    Box::new(foo)
}
example::function2:
        # Stack frame for backtraces
        push    rbp
        mov     rbp, rsp
        # Allocate 8 bytes (edi) with 4 bytes alignment (esi)
        mov     edi, 8
        mov     esi, 4
        call    __rust_allocate@PLT
        # If null, out of memory error
        test    rax, rax
        je      .LBB1_2
        # 64-bit assignment, this is pair of 24 and 42
        movabs  rcx, 103079215146 # 0x000000180000002A
        # 64-bit write to heap allocated storage
        # (x86_64 allows unaligned writes, not sure why compiler assumes this
        #  can be aligned to 8 bytes, when only 4 bytes alignment was requested)
        mov     qword ptr [rax], rcx
        # end the function, output pointer is already correctly stored in rax
        pop     rbp
        ret
# Out of memory somehow happened
.LBB1_2:
        call    alloc::oom::oom@PLT
4 Likes

So I am assuming this will solve the problem
copyless crate

https://github.com/kvark/copyless

but i still agree this features should be the default behavior of the compiler as long as its safe, i feel like in rust we cannot live without crates

1 Like

The copyless readme says "at least until the compiler gets smart enough", so I don't think they are disagreeing with you.

Here is a relevant Rust issue:
https://github.com/rust-lang/rust/issues/41160