What does the box keyword do?

Rust

Rust Problem Overview


In Rust, we can use the Box<T> type to allocate things on the heap. This type is used to safely abstract pointers to heap memory. Box<T> is provided by the Rust standard library.

I was curious about how Box<T> allocation is implemented, so I found its source code. Here is the code for Box<T>::new (as of Rust 1.0):

impl<T> Box<T> {
    /// Allocates memory on the heap and then moves `x` into it.
    /// [...]
    #[stable(feature = "rust1", since = "1.0.0")]
    #[inline(always)]
    pub fn new(x: T) -> Box<T> {
        box x
    }
}

The only line in the implementation returns the value box x. This box keyword is not explained anywhere in the official documentation; in fact it is only mentioned briefly on the std::boxed documentation page.

Rust Solutions


Solution 1 - Rust

NOTE: This reply is a bit old. Since it talks about internals and unstable features, things have changed a little bit. The basic mechanism remains the same though, so the answer is still capable of explaining the underlying mechanisms of box.

What does box x usually uses to allocate and free memory?

The answer is the functions marked with lang items exchange_malloc for allocation and exchange_free for freeing. You can see the implementation of those in the default standard library at heap.rs#L112 and heap.rs#L125.

In the end the box x syntax depends on the following lang items:

  • owned_box on a Box struct to encapsulate the allocated pointer. This struct does not need a Drop implementation, it is implemented automatically by the compiler.
  • exchange_malloc to allocate the memory.
  • exchange_free to free the previously allocated memory.

This can be effectively seen in the lang items chapter of the unstable rust book using this no_std example:

#![feature(lang_items, box_syntax, start, no_std, libc)]
#![no_std]

extern crate libc;

extern {
    fn abort() -> !;
}

#[lang = "owned_box"]
pub struct Box<T>(*mut T);

#[lang = "exchange_malloc"]
unsafe fn allocate(size: usize, _align: usize) -> *mut u8 {
    let p = libc::malloc(size as libc::size_t) as *mut u8;

    // malloc failed
    if p as usize == 0 {
        abort();
    }

    p
}
#[lang = "exchange_free"]
unsafe fn deallocate(ptr: *mut u8, _size: usize, _align: usize) {
    libc::free(ptr as *mut libc::c_void)
}

#[start]
fn main(argc: isize, argv: *const *const u8) -> isize {
    let x = box 1;

    0
}

#[lang = "stack_exhausted"] extern fn stack_exhausted() {}
#[lang = "eh_personality"] extern fn eh_personality() {}
#[lang = "panic_fmt"] fn panic_fmt() -> ! { loop {} }

Notice how Drop was not implemented for the Box struct? Well let's see the LLVM IR generated for main:

define internal i64 @_ZN4main20hbd13b522fdb5b7d4ebaE(i64, i8**) unnamed_addr #1 {
entry-block:
  %argc = alloca i64
  %argv = alloca i8**
  %x = alloca i32*
  store i64 %0, i64* %argc, align 8
  store i8** %1, i8*** %argv, align 8
  %2 = call i8* @_ZN8allocate20hf9df30890c435d76naaE(i64 4, i64 4)
  %3 = bitcast i8* %2 to i32*
  store i32 1, i32* %3, align 4
  store i32* %3, i32** %x, align 8
  call void @"_ZN14Box$LT$i32$GT$9drop.103617h8817b938807fc41eE"(i32** %x)
  ret i64 0
}

The allocate (_ZN8allocate20hf9df30890c435d76naaE) was called as expected to build the Box, meanwhile... Look! A Drop method for the Box (_ZN14Box$LT$i32$GT$9drop.103617h8817b938807fc41eE)! Let's see the IR for this method:

define internal void @"_ZN14Box$LT$i32$GT$9drop.103617h8817b938807fc41eE"(i32**) unnamed_addr #0 {
entry-block:
  %1 = load i32** %0
  %2 = ptrtoint i32* %1 to i64
  %3 = icmp ne i64 %2, 2097865012304223517
  br i1 %3, label %cond, label %next

next:                                             ; preds = %cond, %entry-    block
  ret void

cond:                                             ; preds = %entry-block
  %4 = bitcast i32* %1 to i8*
  call void @_ZN10deallocate20he2bff5e01707ad50VaaE(i8* %4, i64 4, i64 4)
  br label %next
}

There it is, deallocate (ZN10deallocate20he2bff5e01707ad50VaaE) being called on the compiler generated Drop!

Notice even on the standard library the Drop trait is not implemented by user-code. Indeed Box is a bit of a magical struct.

Solution 2 - Rust

Before box was marked as unstable, it was used as a shorthand for calling Box::new. However, it's always been intended to be able to allocate arbitrary types, such as Rc, or to use arbitrary allocators. Neither of these have been finalized, so it wasn't marked as stable for the 1.0 release. This is done to prevent supporting a bad decision for all of Rust 1.x.

For further reference, you can read the RFC that changed the "placement new" syntax and also feature gated it.

Solution 3 - Rust

box does exactly what Box::new() does - it creates an owned box.

I believe that you can't find implementation of box keyword because currently it is hardcoded to work with owned boxes, and Box type is a lang item:

#[lang = "owned_box"]
#[stable(feature = "rust1", since = "1.0.0")]
#[fundamental]
pub struct Box<T>(Unique<T>);

Because it is a lang item, the compiler has special logic to handle its instantiation which it can link with box keyword.

I believe that the compiler delegates box allocation to functions in alloc::heap module.

As for what box keyword does and supposed to do in general, Shepmaster's answer describes perfectly.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionGeorge HilliardView Question on Stackoverflow
Solution 1 - RustDenilson AmorimView Answer on Stackoverflow
Solution 2 - RustShepmasterView Answer on Stackoverflow
Solution 3 - RustVladimir MatveevView Answer on Stackoverflow