What type do you use, when you don't need a type?

Let's say I want to build a Vec of fat pointers which can be literally any type—the only important thing is that the Vec lives for a time, and later is dropped to run all the destructors and clean everything up. What's the right type to use?

I've been using dyn Send since all the types thus far just so happen to be Send, but who knows how long that luck will last.

Using dyn Drop is a no-go since a Drop bound doesn't mean what you think it means.

I'm aware of Any, but it has a restrictive 'static lifetime bound, and I also don't want to pull in RTTI stuff if I don't have to.

Here's my current best solution (also as a playground link):

fn main() {
    let mut drop_later: Vec<Box<dyn Send>> = Vec::new();
    drop_later.push(Box::new(std::fs::File::open("/tmp/abc.txt")));
    drop_later.push(Box::new(std::net::TcpStream::connect("example.com")));
    // For example purposes only. Feel free to imagine something more useful than "6"
    drop_later.push(Box::new(6));

    // Now it's later!
    drop(drop_later);
}

dyn Send works, but kind of obscures the real purpose. Can anyone improve on this?

1 Like

I'm not familiar with any such standard trait, but you can make your own.

trait Dropable {} 
impl<T> Dropable for T {} 

fn main() {
    let mut drop_later: Vec<Box<dyn Dropable>> = Vec::new();
    drop_later.push(Box::new(std::fs::File::open("/tmp/abc.txt")));
    drop_later.push(Box::new(std::net::TcpStream::connect("example.com")));
    // For example purposes only. Feel free to imagine something more useful than "6"
    drop_later.push(Box::new(6));

    // Now it's later!
    drop(drop_later);
}

playground

3 Likes

This could even be unsized, impl<T: ?Sized> Dropable for T {}

If auto traits were stable, you could also use those:

auto trait Droppable {}

Then, you can easily opt out of implementing it for some type too.

2 Likes

I played around with this a bit on rust.godbolt.org. Looks like dyn Any has more overhead than a custom Droppable trait, but not by much. Droppable and Send both result in a vtable with 3 entries (per type used, and they're not deduped). Any adds a 4th entry (8 bytes) pointing to an 11-byte type_id function. (I'm know I'm really pinching pennies here :smile:)

The ideal would be some trait already laying around that I could re-use, but barring that, Droppable seems like the way to go.

From memory, the three vtable slots are size and alignment (for allocation layout purposes), and a pointer to some drop code (for std::intrinsics::drop_in_place()). That's about the smallest you can get with a trait object.

You could always use the system allocator and a void * pointer, using free() from libc to free the memory afterwards. But any size improvements you get by omitting vtables (if the compiler/linker can omit them ata ll) would be vastly outweighed by adding a dependency on the libc crate.

1 Like

If you really want to have a single pointer in the Vec, you can use Vec<Box<Box<dyn Droppable>>> as well. That requires a double allocation indirection though, so is probably worse. Something like typemap may be able to dedupe the size/alignment/drop metadata, but the other overhead almost certainly outweighs that.

1 Like

Another way to get slim pointers is to embed the metadata into the heap data, which can be done with a hundred lines of (unsafe!) code:

EDIT: Added stride in the size calculation

I'm not sure this is 100% correct, Miri flags the deallocation. The error says "expected size 28 and align 8, got size 32 and align 8" for the i32 and WithMetadata::<i32> == 32 but box_size == 28.

Why not store size_of::<WithMetadata<T>>()? Wouldn't it simplify things?

1 Like

Indeed, I had forgotten to take the stride into account for the size calculation; I did some MIRI tests but with other values, and just added the i32 at the end, which featured a non-null stride.

It would indeed, and to future-proof against a change w.r.t. stride/size interaction, it is even advisable; however, for a PoC, having redundant information in the struct is a bit saddening.

1 Like