SmallBox: Box on stack!

#Announcing SmallBox
https://crates.io/crates/smallbox

Box dynamically-sized types on stack

A small crate that allow storing dst on stack and return from function, typically when you want to use dynamically dispatch without heap. Still it has heap fallback in case no enough space for stack allocation.

Example

The simplest usage can be trait object dynamic-dispatch

use smallbox::StackBox;
 
let val: StackBox<PartialEq<usize>> = StackBox::new(5usize).unwrap();
 
assert!(*val == 5)

Another use case is to allow returning capturing closures without having to box them. (impl trait of course!)

use smallbox::StackBox;

fn make_closure(s: String) -> StackBox<Fn()->String> {
    StackBox::new(move || format!("Hello, {}", s)).ok().unwrap()
}

let closure = make_closure("world!".to_owned());
assert_eq!(closure(), "Hello, world!");

Heap fallback involved

use smallbox::SmallBox;

let tiny: SmallBox<[u64]> = SmallBox::new([0; 2]);
let big: SmallBox<[u64]> = SmallBox::new([1; 8]);

assert_eq!(tiny.len(), 2);
assert_eq!(big[7], 1);

match tiny {
    SmallBox::Stack(val) => assert_eq!(*val, [0; 2]),
    _ => unreachable!()
}

match big {
    SmallBox::Box(val) => assert_eq!(*val, [1; 8]),
    _ => unreachable!()
}
14 Likes

For C++ users, this is somewhat like small type optimization of std::function, except it doesn't have to be used with functions. Figured I would mention that, in event somebody would search for this module.

It's somewhat unfortunate that this requires nightly Rust, but I can tell why. Looking forward to seeing better DST support in stable Rust.

I will ask one question. What is the purpose of this line?

// force alignment to be usize
_align: [usize; 0],

When you have this:

space: Space,

And Space is defined to be.

const DEFAULT_SIZE: usize = 4 + 1;

type Space = [usize; DEFAULT_SIZE];

For me it seems like it's already usize-aligned (PhantomData has 0 size).

Thanks for your time reading the source code.

The force align may seem useless now, but it will make sense once user can configure space size. And that is marked at Roadmap section on readme page.

2 Likes

Code looks nice, not sure about names.
Box says to me stored on heap Box<StackBox<_>> this even does so.
Space so far to me seems more appropriate name. (Store, Fill) sure there is likely to be others more appropriate.
SmallBox just has the minor irony of being a bigger size than Box.

What would be good is if the compiler could check the size and error if over, instead of the runtime use of Result.

1 Like

I hope we could check the size at compile time, but we can not until mem::size_of become const fn and const dependent type is landed.

The name smallbox comes mainly from the popular libarary smallvec, which have the relevant purpose - stack for small and heap for big.

Moreover, the box name sound like to wrap some thing up and to give back when needed, and, for DST, which can transform some unsize to sized. It does not particularly mean a heap allocation, I think. What do you think of this?

4 Likes

I also think the name is unfortunate; unlike Vec, whose primary association is "contiguous array in memory", Box in Rust is strongly associated with "on the heap". Since this is expressly for DSTs, why not turn it around and call it stack-dst or similar?

1 Like

The name stack-dst is already taken, which inspired this crate. You can see this as an improvement. Maybe stackbox sounds better, which, I mean, emphasizes stack? After all, smallbox may actually store thing on heap, right? You can think that it is just an small optimization.

1 Like

Maybe rename Stackbox to Space and leave others unchanged?

2 Likes

what about shoebox :smile:

5 Likes

I don't see the problem with using the word Box. To me, I Box something not because I want it on the heap, but because I want to be able to do dynamic dispatch and so I need a container (box) for it. Being on the heap is an implementation detail.

I think StackBox is a good name.

9 Likes

smallbox now supports custom capacity!!! :heart_eyes::heart_eyes:

Postpone renaming matter in order to get more advice.

2 Likes

I can't wait for π-types to stabilize. These kinds of APIs will look and work so much better

2 Likes

I'm wondering if small box does what you want it to do... doesn't the whole stack get allocated for the function body, so transforming the type's size just means that the stack ALWAYS allocates the larger space?

Maybe there are compiler tricks I don't know about

1 Like

Is there even a feature to try currently?

1 Like

You are right, that's what the capacity of StackBox means. It always allocate as big as type Space on stack. The method resize() is used to pass StackBox or SmallBox through functions with different capacity bound.

1 Like

It's already available on crate.io.

I was responding the π-types-statement. I clarified my post.

1 Like

Not AFAIK

1 Like

This is very nice!

Some questions:

Why do you use the box syntax? Ie. why not just use Box::new()?

In space.rs. why use a struct with a field marked as #[used] as opposed to using a struct-tuple? (ie. eg. pub struct S2([usize; 2]). Come to think of it, why not even use type aliases for those?

(Maybe all my suggestions are stupid, in which case I apologize, I'm just being curious.)

box syntax is used because it performs Unsize coercion, unlike Box::new.

It's marked as #[used] to avoid compiler warnings that this type is never used. Although I suppose it could have been called _inner instead of inner to avoid compiler warnings (underscore variables are implicitly used).

Having it as type alias would cause it to be an useful type when it wasn't meant to be used as such. This only represents space usage, not an array of usize.