Question
How can a function take ownership of a Box<T> and return the owned T inside it?
fn unbox<T>(value: Box<T>) -> T {
// What should go here?
}
Box::into_raw appears related, and this code type-checks only inside an unsafe block:
fn unbox<T>(value: Box<T>) -> T {
unsafe { *value.into_raw() }
}
Is that the correct implementation? Why does dereferencing the raw pointer require unsafe, and what does that mean for how Box<T> works?
Short Answer
You can move a value out of an owned Box<T> with *value:
fn unbox<T>(value: Box<T>) -> T {
*value
}
This is safe Rust. Box::into_raw is for low-level ownership transfer and is not needed when you simply want the value inside the box.
Concept
A Box<T> is an owning smart pointer. It stores a T on the heap and ensures that the heap allocation is cleaned up when the Box is dropped.
When a function receives a Box<T> by value, it owns that box:
fn unbox<T>(value: Box<T>) -> T {
*value
}
The expression *value dereferences the box and moves its contained T out. Because the box is owned, Rust knows this move is valid. It also knows that it must release the box's heap allocation afterward.
This differs from dereferencing a borrowed box:
fn read<T>(value: &Box<T>) -> &T {
&**value
}
A borrowed Box<T> cannot generally give away its T, because the caller still owns the box.
Box::into_raw intentionally changes the situation. It consumes the Box<T> and returns a raw pointer, *mut T. At that point, Rust no longer automatically manages the allocation. The caller becomes responsible for using the pointer correctly and eventually reclaiming it, usually with Box::from_raw.
Mental Model
Think of Box<T> as a labelled, managed storage crate containing an item.
- Owning a
Box<T>means you hold the crate and are responsible for it. - Writing
*valuemeans: “Open the crate, take the item, and let Rust dispose of the now-empty crate correctly.” - Calling
Box::into_raw()means: “Remove the management label and receive only the crate's storage address.”
After receiving a raw address, Rust cannot verify whether the address is valid, whether it has already been freed, or who is responsible for cleaning it up. That is why using raw pointers requires unsafe.
Syntax and Examples
The normal, safe implementation is:
fn unbox<T>(value: Box<T>) -> T {
*value
}
Example:
fn main() {
let number = Box::new(42);
let result = unbox(number);
println!("{result}");
}
fn unbox<T>(value: Box<T>) -> T {
*value
}
Output:
42
number cannot be used after the call because the Box<i32> was moved into unbox.
This also works for types that are not Copy:
fn main() {
let message = Box::new(String::from("hello"));
let message: String = *message;
println!("{message}");
}
The String is moved out of the box; it is not cloned.
Step by Step Execution
Consider this code:
fn main() {
let boxed = Box::new(String::from("Rust"));
let text = unbox(boxed);
println!("{text}");
}
fn unbox<T>(value: Box<T>) -> T {
*value
}
Execution:
String::from("Rust")creates aStringvalue.Box::new(...)places thatStringin a heap allocation and returnsBox<String>.- Passing
boxedtounboxmoves ownership of theBox<String>into the parametervalue. *valueaccesses theStringstored by the box and moves it into the function's return value.- Rust releases the box allocation. The returned
Stringremains valid because ownership was moved totext. println!readstext.- At the end of
main,textis dropped, which cleans up theString's own contents.
The key point is that the box allocation and the value inside it have related but distinct cleanup responsibilities. Moving out T transfers ownership of T, while Rust still correctly frees the box allocation.
Real World Use Cases
Moving a value out of a box is useful when heap allocation was needed temporarily but the next stage can own the value directly.
- Recursive data structures: A tree node may store children as
Box<Node>. When consuming the tree, code can move a child node out of its box. - Trait objects: A function may receive
Box<dyn Trait>and consume it to transfer ownership to another component. - Parser or compiler structures: Recursive expressions are often boxed. A transformation pass may consume a
Box<Expr>and return anExpr. - Task or message processing: A queue may contain boxed jobs. A worker can take ownership of a job and execute it.
- Reducing nesting: An API may return
Box<ResultData>for a recursive or dynamically sized design, while later code needs the ownedResultData.
Real Codebase Usage
In application code, developers usually write *boxed only when they truly need to consume the box and obtain its value.
A common pattern is destructuring a boxed enum in a match:
enum Expression {
Number(i32),
Negate(Box<Expression>),
}
fn evaluate(expression: Expression) -> i32 {
match expression {
Expression::Number(value) => value,
Expression::Negate(inner) => -evaluate(*inner),
}
}
inner is a Box<Expression>. *inner safely moves the contained Expression into evaluate.
Use Box::into_raw mainly at low-level boundaries, such as foreign-function interfaces (FFI), where another language temporarily receives an opaque pointer:
let boxed = Box::new(String::from("owned by Rust"));
let pointer: *mut String = Box::into_raw(boxed);
// Later, exactly once, when ownership returns to Rust:
unsafe {
drop(Box::from_raw(pointer));
}
This pattern has a strict ownership contract: the raw pointer must be converted back exactly once, using the matching type and allocator assumptions.
Common Mistakes
Using into_raw just to extract the value
This compiles, but it is not the right implementation:
fn unbox<T>(value: Box<T>) -> T {
unsafe { *value.into_raw() }
}
The raw pointer was created from a box, but no Box::from_raw is called to reclaim the box allocation. The T is moved out, while the allocation itself is leaked.
Use this instead:
fn unbox<T>(value: Box<T>) -> T {
*value
}
Trying to move out through a borrow
This does not work for a non-Copy type:
fn broken(value: &Box<String>) -> String {
**value
}
The function only borrowed the box, so it cannot move out the String. Accept Box<String> by value if the function should take ownership, or clone deliberately:
fn copy_text(value: &Box<String>) -> String {
(**value).clone()
}
Assuming unsafe makes code automatically correct
An unsafe block tells the compiler that you, the programmer, are taking responsibility for the operation's safety requirements. It does not validate that those requirements are actually met.
Rebuilding a box more than once
This is undefined behavior:
let pointer = Box::into_raw(Box::new(10));
unsafe {
let first = Box::from_raw(pointer);
let second = Box::from_raw(pointer); // Invalid: same allocation owned twice
drop(first);
drop(second);
}
A pointer returned by Box::into_raw must be turned back into a Box at most once.
Comparisons
| Operation | Ownership result | Safe? | Typical purpose |
|---|---|---|---|
*boxed where boxed: Box<T> | Moves out T; box allocation is cleaned up | Yes | Consume a box and get its value |
&*boxed | Borrows T | Yes | Read or pass the inner value without consuming the box |
Box::into_raw(boxed) | Returns *mut T; caller manages allocation | Yes to call | FFI and low-level ownership transfer |
*raw_pointer | Reads or moves through a raw pointer | No, requires unsafe | Low-level code with manually proven pointer validity |
Box::from_raw(pointer) | Restores Box<T> ownership | No, requires unsafe | Reclaim a pointer previously produced by into_raw |
Box<T> dereferencing is safe because Rust tracks its ownership. Raw-pointer dereferencing is unsafe because raw pointers do not carry checked ownership or validity guarantees.
Cheat Sheet
// Consume a Box<T> and return T.
fn unbox<T>(value: Box<T>) -> T {
*value
}
Box<T>owns a heap-allocatedT.- If you own the
Box<T>,*boxedcan move out the innerT. - If you only have
&Box<T>, you can borrow with&**boxed, but cannot move out a non-Copyvalue. Box::into_raw(boxed)consumes the box and yields*mut T.- Dereferencing
*mut Trequiresunsafebecause Rust cannot verify pointer validity. - A pointer from
Box::into_rawshould generally be recovered exactly once withBox::from_raw(pointer). - Do not use
into_rawwhen*boxedis sufficient.
FAQ
Can I write *value without an unsafe block?
Yes. When value is an owned Box<T>, Rust safely moves out the contained T.
Why does *value.into_raw() require unsafe?
into_raw() returns a raw pointer. Rust cannot guarantee that arbitrary raw pointers are valid to dereference, so dereferencing one requires an explicit unsafe block.
Does *boxed clone the inner value?
No. It moves the inner value out of the box. No Clone implementation is required.
Does moving out of a box leak memory?
No. *boxed is handled safely by Rust: the inner value is moved, and the box allocation is released. Using into_raw without restoring ownership can leak the allocation.
Can I move a value out of &Box<T>?
Not in general. A shared borrow does not grant ownership. You can borrow the inner value, or clone it if cloning is appropriate.
When should I use Box::into_raw?
Use it when ownership must cross a low-level boundary, commonly an FFI boundary. You must clearly define who eventually calls Box::from_raw or otherwise correctly deallocates the allocation.
Is Box::into_raw itself unsafe?
No. Creating a raw pointer is safe. Operations that rely on the pointer being valid, such as dereferencing it or converting it back with Box::from_raw, are unsafe.
Mini Project
Description
Build a small recursive expression evaluator. Recursive enum variants need indirection, so child expressions use Box<Expression>. Evaluating a negation consumes its boxed child with *inner.
Goal
Evaluate a recursive arithmetic expression while safely moving values out of Box<Expression>.
Requirements
Create an Expression enum with numeric, addition, and negation variants.
Store recursive child expressions in Box<Expression>.
Write an evaluate function that consumes an Expression and returns an i32.
Use *inner to move a boxed child expression into a recursive call.
Do not use unsafe or Box::into_raw.
Keep learning
Related questions
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.