Как получить владеющее значение из `Box<T>` в Rust без копирования?
Используйте Box::into_inner() (стабильный метод с Rust 1.36)
Начиная с Rust 1.36, в стандартной библиотеке есть прямой метод Box::into_inner(boxed). Он делает именно то, что вам нужно: потребляет Box и возвращает владеющее значение, которое в нём хранилось. Никакого копирования или unsafe — всё безопасно и эффективно.
let boxed = Box::new(Data { numbers: vec![1, 2, 3] });
let data = Box::into_inner(boxed); // data: Data — владеющее значение
println!("{:?}", data.numbers); // Работает!
// boxed больше не доступен — moved
Альтернатива: разыменование через * с перемещением
Если ваш тип реализует трейт Copy (например, i32, bool, кортежи из них), то можно просто написать let value = *boxed;. Но для вашего случая (структура с Vec) это не сработает, так как Data не Copy. Компилятор выдаст ошибку:
error[E0507]: cannot move out of dereference of `Box<Data>`
--> src/main.rs:8:20
|
8 | let data = *boxed;
| ^^^^^^ move occurs because value has type `Data`, which does not implement the `Copy` trait
Когда Box::into_inner недоступен (старые версии Rust)
Если вы работаете с Rust старше 1.36, есть два безопасных способа:
- Использовать
Box::into_rawиBox::from_raw— это unsafe, но контролируемо:let ptr = Box::into_raw(boxed); // получаем сырой указатель let data = unsafe { Box::from_raw(ptr) }; // восстанавливаем Box и сразу разбираем // Теперь у нас data: Box<Data>, нужно извлечь значение let data = *data; // Это сработает, так как Box потреблёнНо проще сразу сделать
let data = *unsafe { Box::from_raw(Box::into_raw(boxed)) };— однако это избыточно, если доступенinto_inner. - Использовать
std::mem::replaceс временной заглушкой — если ваш тип реализуетDefault:let mut boxed = Box::new(Data { numbers: vec![1, 2, 3] }); let data = std::mem::replace(&mut *boxed, Data::default()); // заменили на пустой Data // Теперь data содержит оригинал, boxed — пустышкуЭтот способ не требует unsafe, но требует
Defaultи создаёт временную аллокацию (в вашем случае — пустойVec).
Почему Box::into_inner — лучший выбор?
Он специально спроектирован для этой задачи:
- Не требует никаких трейтов (
Copy,Default). - Не создаёт лишних аллокаций.
- Полностью безопасен — компилятор гарантирует корректность.
- Код читается очевидно: «преврати Box во внутреннее значение».
В вашем случае решение будет таким:
struct Data {
numbers: Vec<i32>,
}
fn main() {
let boxed = Box::new(Data { numbers: vec![1, 2, 3] });
let data = Box::into_inner(boxed);
println!("{:?}", data.numbers); // [1, 2, 3]
// boxed больше не существует
}
Это идиоматичный, безопасный и эффективный способ. Помните: Box — это просто указатель на кучу, и into_inner перемещает значение обратно в стек (или в другое место), освобождая память кучи.
Альтернативный подход: Использование std::mem::ManuallyDrop и сырых указателей
Если вам по каким-то причинам нужно больше контроля (например, вы работаете с FFI или сложными структурами данных), можно использовать ManuallyDrop в паре с Box::into_raw. Этот метод полезен, когда вы хотите извлечь значение, но при этом сохранить указатель на память для последующего ручного управления.
use std::mem::ManuallyDrop;
let boxed = Box::new(Data { numbers: vec![1, 2, 3] });
let ptr = Box::into_raw(boxed); // сырой указатель, память не освобождена
let data = unsafe { ManuallyDrop::new(std::ptr::read(ptr)) }; // читаем значение
// Теперь data: ManuallyDrop<Data>, и мы владеем значением
// Но нужно не забыть освободить память!
unsafe { std::alloc::dealloc(ptr as *mut u8, std::alloc::Layout::new::<Data>()); }
// Или, если хотим вернуть управление Box:
// let _ = unsafe { Box::from_raw(ptr) }; // восстановит Box и удалит при выходе из области
Плюсы: Полный контроль над памятью, можно извлечь значение, не вызывая деструктор Box (полезно для FFI).
Минусы: Требует unsafe, легко допустить утечку памяти или двойное освобождение. Для 99% случаев Box::into_inner предпочтительнее.
В реальных проектах старайтесь избегать этого подхода, если нет веской причины (например, работа с C-библиотеками). Для обычного извлечения значения из Box всегда используйте Box::into_inner — это безопасно, просто и эффективно.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги