Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does the box keyword do?

Tags:

rust

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.

like image 917
George Hilliard Avatar asked May 20 '15 14:05

George Hilliard


People also ask

What does box do in Rust?

All values in Rust are stack allocated by default. Values can be boxed (allocated on the heap) by creating a Box<T> . 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.


1 Answers

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.

like image 142
Denilson Amorim Avatar answered Sep 22 '22 13:09

Denilson Amorim