When Should I Use Box<T>


#1

This is probably a pretty open-ended question, but in all the Rust code I’ve been writing I’ve never needed to use the Box<T> type myself (I know it gets used in the standard library).

Is there a time when I should prefer heap allocation over the stack?


#2

The only time I’ve ever needed Box for purely safe code is for owned trait objects, i.e. an owned version of &T where T is a trait. In those cases you need Box because it’s the only way to own a trait object (or any unsized type). In my case I do wind up using it in places where I want to keep a pointer to an object for unsafe code and need to know that regardless of ownership the actual data doesn’t move in memory.


#3

That makes sense. So it’s necessarily something you’d pull out of the hat if you’re only dealing with safe, sized types.

Is there some kind of benefit to using Box if you’re expecting an object to be passed around/reallocated a lot? (Like the custom P type in libsyntax)


#4

The two major cases for Box<T> are:

  • recursive types, like a list enum List<T> { Nil, Cons(T, Box<List<T>>) }, to avoid them being infinite size (this is what libsyntax’s P is for)
  • owned trait objects as @excaliburHisSheath mentions

There are some more niche cases, such as:

  • atomic/lock-free operations where CPUs generally only support atomic operations on things that are pointer sized (or less),
  • for performance if T is large and is being moved around a lot, using a Box<T> instead will avoid doing big memcpys

The first one is generally handled by low-level libraries such as crossbeam (i.e. the APIs of such libraries will make the decisions Boxing or not, and you don’t have to think, just follow their lead), and the second is reasonably rare: the cost of dynamic allocation and the (typically) poorer cache locality will mean Box<T> is usually slower than just T itself.


#5

Thanks for explaining, I didn’t want to feel like I was missing out on something :slightly_smiling:

I’ll have to find some profiling tools I can use to try and get a sense of what my Rust programs are actually doing at a point in time to understand why the options are there.

As my first real foray into a non-.NET language it still feels a bit magic.


#6

If you know that you will allocate a lot, you might prefer avoiding allocating on the stack in order not to hit the stack limit and have a stack overflow. Note that Rust by default has a pretty big stack.


#7

Thanks for clarifying that. Like all things it sounds like ‘it depends’


#8

The simplest example on how somebody could hit this problem is when allocating buffers on the stack.
To give some context, in the issue I linked, the problem was that vagga uses musl which has 80 KB stack size instead of glibc’s 8 MB.
Also, that stack overflow is still safe since Rust adds a guard at the end of the stack.