bumpalo icon indicating copy to clipboard operation
bumpalo copied to clipboard

Can't make a boxed fn, `Box<'_, dyn Fn()>`

Open BrandonDyer-Disney opened this issue 11 months ago • 9 comments

Note: I renamed my bumpalo import Box to BumpBox for clarity.

let bump = Bump::new();

let f = || ();
let boxed = BumpBox::new_in(f, &bump);
let boxed: BumpBox<'_, dyn Fn()> = boxed;

I see no reason why I shouldn't be able to do this, but alas the compiler just won't have it.

error[E0308]: mismatched types
   --> src/main.rs:114:40
    |
112 |     let f = || ();
    |             -- the found closure
113 |     let boxed = BumpBox::new_in(f, &bump);
114 |     let boxed: BumpBox<'_, dyn Fn()> = boxed;
    |                ---------------------   ^^^^^ expected `Box<'_, dyn Fn()>`, found `Box<'_, {[email protected]:112:13}>`
    |                |
    |                expected due to this
    |
    = note: expected struct `bumpalo::boxed::Box<'_, dyn Fn()>`
               found struct `bumpalo::boxed::Box<'_, {closure@src/main.rs:112:13: 112:15}>`

error: Recipe `run` failed on line 34 with exit code 127

So, it's totally fine with me having a Box<'_, {closure}>, but not a Box<'_, dyn Fn()>.

It works just fine if I use std's Box:

let f = || ();
let boxed = Box::new(f);
let boxed: Box<dyn Fn()> = boxed;

BrandonDyer-Disney avatar May 04 '25 22:05 BrandonDyer-Disney

This would require the unsized_locals unstable Rust feature: https://doc.rust-lang.org/unstable-book/language-features/unsized-locals.html

The standard library can use unstable features, but bumpalo cannot in general.

fitzgen avatar May 05 '25 20:05 fitzgen

@fitzgen Thanks for the response. Do you know of any workaround for this? Even if I have to do something disgusting and/or unsafe, I'm willing to. I just need some way to store a closure in a way I can later call drop (or std::mem::drop_in_place) on, even manually, regardless of boxes.

My requirements are just that I need to store some kind of dynamic dispatch dyn Fn() reference in a Slotmap, and I'd really like to have the capture stored in a Bump.

BrandonDyer-Disney avatar May 05 '25 20:05 BrandonDyer-Disney

This doesn't work because Box implements CoerceUnsized (a nightly trait) which bumpalo's Box doesn't. I've seen the allocator_api2's unsize_box macro which allows such a coercion in stable rust. Afaik the same method would work for bumpalo with code like this:

macro_rules! unsize_box {
    ($boxed:expr $(,)?) => {{
        let (ptr, lt) = box_into_raw_with_lifetime($boxed);
        let ptr: *mut _ = ptr;
        unsafe { box_from_raw_with_lifetime(ptr, lt) }
    }};
}

pub fn box_into_raw_with_lifetime<'a, T: ?Sized>(
    boxed: bumpalo::boxed::Box<'a, T>,
) -> (*mut T, &'a ()) {
    (bumpalo::boxed::Box::into_raw(boxed), &())
}

pub unsafe fn box_from_raw_with_lifetime<'a, T: ?Sized>(
    ptr: *mut T,
    _lifetime: &'a (),
) -> bumpalo::boxed::Box<'a, T> {
    bumpalo::boxed::Box::from_raw(ptr)
}

fn test() {
    let bump = bumpalo::Bump::new();

    let f = || ();
    let boxed = bumpalo::boxed::Box::new_in(f, &bump);
    let boxed: bumpalo::boxed::Box<dyn Fn()> = unsize_box!(boxed);
}

EDIT: whoops fixed the code

bluurryy avatar May 05 '25 20:05 bluurryy

I need to store some kind of dynamic dispatch dyn Fn() reference in a Slotmap

Does the map need to have Box<dyn Fn()> values, or would it be fine having &'a dyn Fn() values? If the latter works, then you can allocate Box<MyConcreteFn> in a Bump and then create &'a dyn Fn() references afterwards and put those in the map.

fitzgen avatar May 05 '25 20:05 fitzgen

Does the map need to have Box<dyn Fn()> values, or would it be fine having &'a dyn Fn() values? If the latter works, then you can allocate Box<MyConcreteFn> in a Bump and then create &'a dyn Fn() references afterwards and put those in the map.

A reference is fine as long as I have a way to call drop on them.

BrandonDyer-Disney avatar May 05 '25 21:05 BrandonDyer-Disney

@bluurryy Thanks for your comprehensive reply. This looks promising, and I'll see if I can use this. I'd be worried about the from_raw not knowing the size of what it's pointing too though. Is calling drop on such a thing safe?

BrandonDyer-Disney avatar May 05 '25 21:05 BrandonDyer-Disney

Yes using this macro is safe. This will only allow a conversion to a type which it can safely coerce to. Essentially the same thing is happening as with the standard box coercion.

EDIT: the coercion from pointer to pointer let ptr: *mut _ = ptr; will add the correct vtable to create the fat pointer which includes the size, drop info and so on

bluurryy avatar May 05 '25 21:05 bluurryy

What's the possibility of adding a method for this?

let bump = Bump::new();

let f = || ();
let boxed = Box::new_in(f, &bump);
let boxed: Box<dyn Fn()> = unsafe { boxed.unsize() }; // Would it even need to be unsafe?

BrandonDyer-Disney avatar Jun 27 '25 21:06 BrandonDyer-Disney

Using the nightly feature unsize, using the Unsize trait, you could write such a method (and that method would be safe). But when you're already using nightly features you might as well implement CoerceUnsized for Box so it works just like std's box.

Only the macro approach would work on stable.

bluurryy avatar Jun 27 '25 21:06 bluurryy