Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What happens when a stack-allocated value is boxed?

Tags:

rust

boxing

If we have a value that is already allocated on stack, will boxing copy it to heap and then transfer ownership (that's how it works in .NET, with the exception that both copies will stay alive)? Or will the compiler be "smart" enough to allocate it directly on heap from the beginning?

struct Foo {
    x: i32,
}

fn main() {
    // a is allocated on stack?
    let a = Foo { x: 1 };

    // if a is not used, it will be optimized out
    println!("{}", a.x);

    // what happens here? will the stack allocated structure
    // be moved to heap? or was it originally allocated on heap?
    let b = Box::new(a);
}

I'm not a specialist in assembler, but this looks like it is actually allocated on stack and then moved: http://pastebin.com/8PzsgTJ1. But I need a confirmation from someone who actually knows what is happening.

like image 788
Sergii Bogomolov Avatar asked Apr 13 '15 22:04

Sergii Bogomolov


People also ask

What does box mean in Rust?

A box is a smart pointer to a heap allocated value of type T . When a box goes out of scope, its destructor is called, the inner object is destroyed, and the memory on the heap is freed. Boxed values can be dereferenced using the * operator; this removes one layer of indirection. use std::mem; #[allow(dead_code)]

Is stack memory faster than heap?

The stack is faster because the access pattern makes it trivial to allocate and deallocate memory from it (a pointer/integer is simply incremented or decremented), while the heap has much more complex bookkeeping involved in an allocation or free.

What is heap Rust?

Memory management The stack is very fast, and is where memory is allocated in Rust by default. But the allocation is local to a function call, and is limited in size. The heap, on the other hand, is slower, and is explicitly allocated by your program. But it's effectively unlimited in size, and is globally accessible.

Where is stack memory located?

Stored in computer RAM just like the heap. Variables created on the stack will go out of scope and are automatically deallocated. Much faster to allocate in comparison to variables on the heap.


2 Answers

It would be pretty strange for this optimization to happen as you describe it. For example, in this code:

let a = Foo { x: 1 };
// operation that observes a
let b = Box::new(a);
// operation that observes b

&a and &b would be equal, which would be surprising. However, if you do something similar, but don't observe a:

#[inline(never)]
fn frobnotz() -> Box<Foo> {
    let a = Foo { x: 1 };
    Box::new(a)
}

You can see via the LLVM IR that this case was optimized:

define internal fastcc noalias dereferenceable(4) %Foo* @_ZN8frobnotz20h3dca7bc0ee8400bciaaE() unnamed_addr #0 {
entry-block:
  %0 = tail call i8* @je_mallocx(i64 4, i32 0)
  %1 = icmp eq i8* %0, null
  br i1 %1, label %then-block-106-.i.i, label %"_ZN5boxed12Box$LT$T$GT$3new20h2665038481379993400E.exit"

then-block-106-.i.i:                              ; preds = %entry-block
  tail call void @_ZN3oom20he7076b57c17ed7c6HYaE()
  unreachable

"_ZN5boxed12Box$LT$T$GT$3new20h2665038481379993400E.exit": ; preds = %entry-block
  %2 = bitcast i8* %0 to %Foo*
  %x.sroa.0.0..sroa_idx.i = bitcast i8* %0 to i32*
  store i32 1, i32* %x.sroa.0.0..sroa_idx.i, align 4
  ret %Foo* %2
}

Similarly, you can return the struct on the stack and then box it up, and there will still just be the one allocation:

You may think that this gives us terrible performance: return a value and then immediately box it up ?! Isn't this pattern the worst of both worlds? Rust is smarter than that. There is no copy in this code. main allocates enough room for the box, passes a pointer to that memory into foo as x, and then foo writes the value straight into the Box.

like image 76
Shepmaster Avatar answered Jan 22 '23 02:01

Shepmaster


As explained in the official Rust documentation here, Box<T>::new(x: T) allocates memory on the heap and then moves the argument into that memory. Accessing a after let b = Box::new(a) is a compile-time error.

like image 29
Miles Rout Avatar answered Jan 22 '23 02:01

Miles Rout