What does the box keyword do?

前端 未结 3 1378
故里飘歌
故里飘歌 2021-02-01 12:27

In Rust, we can use the Box type to allocate things on the heap. This type is used to safely abstract pointers to heap memory. Box

相关标签:
3条回答
  • 2021-02-01 13:05

    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 undelying 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.

    0 讨论(0)
  • 2021-02-01 13:15

    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.

    0 讨论(0)
  • 2021-02-01 13:25

    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.

    0 讨论(0)
提交回复
热议问题