Help wanted I would really appreciate if someone could check my explanations for Box and Unbox. This is the best I could come up with based on digging through the implementation and google.
## Allocate a value on the heap. Boxing is an expensive processes as it copies
## the value from the stack to the heap.
##
## expect Box.unbox(Box.box "Stack Faster") == "Stack Faster"
box : a -> Box a
## Return a value to the stack. Unboxing is an expensive processes as it copies
## the value from the heap to the stack.
##
## expect Box.unbox(Box.box "Stack Faster") == "Stack Faster"
unbox : Box a -> a
Yes, that’s correct, but “unbox” only “returns a value” in the sense that it will return a copy of the boxed value; it won’t destroy the box. The upside of using a Box is that sometimes you might have very large values that cost more to copy around the stack (because they must be copied on the stack to be passed between functions, for example) than it is to allocate them on the stack and only copy around the pointer to their memory address.
The actual cost of allocation depends on the underlying allocator, it might actually be very cheap, but those are details that aren’t important for understanding Box.
It seems a bit wrong to say it's slow because of the copying. I wouldn't really expect that to be a big part of the cost, though I'm not the most knowledgeable about that. Isn't it more the allocation itself, and any reference counting you might need from that point on in the program?
(Also "an expensive processes" is a typo. It mixes singular and plural.)
it's a level of indirection, and that has upsides and downsides. This should be a "don't do it if you aren't sure you need it" sort of thing
it exists for interaction with platforms, and in general to get a quick copy of big values: rather than copying all the bytes of the value, you just bump the RC and done. But that only makes sense if the value in question is truly big
oh I actually thought the main use would be to prevent tag union variant sizes from exploding :big_smile:
e.g. boxing one big variant, or a closure
I think mentioning those use cases in the Box
docs would be a good idea, as would mentioning that you only ever need to use Box
as a performance optimization, and by default it makes performance worse, so you should only use it under very specific circumstances!
Performance optimization or if the platform requires it as in the case of having state of unknown type passed to the platform. Required for TEA.
Updated to the following. Does this look better?
## Allocate a value on the heap. Boxing is an expensive process as it copies
## the value from the stack to the heap. This may provide a performance
## optimization for advanced use cases with large values. A platform may require
## that some values are boxed.
##
## expect Box.unbox(Box.box "Stack Faster") == "Stack Faster"
box : a -> Box a
## Returns a boxed value.
##
## expect Box.unbox(Box.box "Stack Faster") == "Stack Faster"
unbox : Box a -> a
I actually just don't agree with the point about it being expensive due to copying and I think it would be better to remove that. Roc is an immutable language. We do copying in all sorts of other operations and we don't call it expensive anywhere else. So I don't like making it into a big deal for this one case where the copying is not particularly special.
It's ok to say it copies from stack to heap, but highlighting that as unusually expensive is incorrect IMO
Last updated: Jul 05 2025 at 12:14 UTC