Подтвердить что ты не робот

Что делает ключевое слово box?

В Rust мы можем использовать тип Box<T> для размещения вещей в куче. Этот тип используется, чтобы безопасно абстрагировать указатели на кучу памяти. Box<T> предоставляется стандартной библиотекой Rust.

Мне было интересно, как реализовано распределение Box<T>, поэтому я нашел его исходный код. Вот код для Box<T>::new (от 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
    }
}

Единственная строка в реализации возвращает значение box x. Это ключевое слово box не объясняется нигде в официальной документации; на самом деле это кратко упоминается только на странице документации std::boxed.

4b9b3361

Ответ 1

Что обычно использует box x для выделения и освобождения памяти?

Ответ - это функции, отмеченные элементами lang exchange_malloc для выделения и exchange_free для освобождения. Вы можете увидеть реализацию в стандартной библиотеке по умолчанию heap.rs#L112 и heap.rs#L125.

В конце синтаксис box x зависит от следующих элементов:

  • owned_box в структуре Box для инкапсуляции выделенного указателя. Эта структура не нуждается в реализации Drop, она автоматически реализуется компилятором.
  • exchange_malloc, чтобы выделить память.
  • exchange_free, чтобы освободить ранее выделенную память.

Это можно эффективно увидеть в главе lang items книги ржавчины, используя этот пример no_std:

#![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 {} }

Обратите внимание, что Drop не реализовано для структуры Box? Хорошо посмотрим на LLVM IR, сгенерированный для 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
}

allocate (_ZN8allocate20hf9df30890c435d76naaE) был вызван как и ожидалось, чтобы построить Box, тем временем... Посмотрите! A Drop для Box (_ZN14Box$LT$i32$GT$9drop.103617h8817b938807fc41eE)! Давайте посмотрим IR для этого метода:

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
}

Вот он, deallocate (ZN10deallocate20he2bff5e01707ad50VaaE) вызывается в компиляторе, сгенерированном Drop!

Обратите внимание даже на стандартную библиотеку Drop черта не реализуется пользовательским кодом. Действительно, Box - это немного магическая структура.

Ответ 2

До того, как box был отмечен как нестойкий, он использовался как сокращение для вызова Box::new. Тем не менее, он всегда был предназначен для того, чтобы иметь возможность выделять произвольные типы, такие как Rc, или использовать произвольные распределители. Ни один из них не был завершен, поэтому он не был отмечен как стабильный для версии 1.0. Это делается для предотвращения поддержки плохого решения для всех Rust 1.x.

Для получения дополнительной информации вы можете прочитать RFC, который изменил синтаксис "размещение нового" , а также включил его.

Ответ 3

box делает именно то, что делает Box::new() - он создает принадлежащую мне коробку.

Я считаю, что вы не можете найти реализацию ключевого слова box, потому что в настоящее время он жестко закодирован для работы с принадлежащими ящикам, а тип box - это элемент lang:

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

Поскольку это элемент lang, у компилятора есть специальная логика для обработки своего экземпляра, который он может связать с ключевым словом box.

Я считаю, что компилятор делегирует выделение ящиков функциям в alloc::heap.

Что касается того, что ключевое слово box делает и должно делать в целом, ответ Shepmaster прекрасно описывает.