6

I want an owned list of Rust trait objects. I could implement it as Vec<Box<dyn Trait>> but that allocates space on the heap for every trait object. What I’d prefer is a CompactList<dyn Trait> type with a memory representation that looks like:

[vtable1, size1, data1, vtable2, size2, data2, vtable3, size3, data3]

size* is the size in bytes of the corresponding data*.

With this, I could create an Iterator<Item = &dyn Trait>. The only operations I need on CompactList<T> are push() and iter().

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Calebmer
  • 2,972
  • 6
  • 29
  • 36
  • You could do that with `std::raw::TraitObject`. – mcarton Mar 18 '20 at 15:03
  • I'm pretty sure any solution is going to involve `unsafe`, rely on nightly features, and very likely to be undefined behavior. – Shepmaster Mar 18 '20 at 15:05
  • @mcarton, that would still only allow you to pack raw pointers together, not the actual data. Although you _could_ pack them as `vtable ptr, size, data`, and then provide an iterator or index, that exposes `raw::TraitObject`s. – Peter Hall Mar 18 '20 at 15:08
  • You may be able to borrow some ideas from untyped arenas, for example [bumpalo](https://docs.rs/bumpalo/3.2.0/bumpalo/), which can allocate differently-typed values in chunks of memory using `unsafe`. – trent Mar 18 '20 at 15:14
  • I wanted to avoid nightly and it didn’t look like there was much appetite for stabilizing `std::raw::TraitObject`. – Calebmer Mar 20 '20 at 16:01

1 Answers1

5

The dynstack crate does what you want. It relies on the representation of fat pointers, which is what trait objects are, and that representation could theoretically change some day.

While it solves the problem of avoiding heap allocations for each object, its in-memory representation is different: Instead of a flat list, there are basically two lists:

  • [data1, data2, ...]
  • [(vtable1, size1), (vtable2, size2), ...]

Since the data structs can have different sizes, your representation doesn't support O(1) random access, while this one does. See this blog post for details.

Example, adapted from the documentation:

use dynstack::{dyn_push, DynStack};
use std::fmt::Debug;

let mut stack = DynStack::<dyn Debug>::new();
dyn_push!(stack, "hello, world!");
dyn_push!(stack, 0usize);
dyn_push!(stack, [1, 2, 3, 4, 5, 6]);

for item in stack.iter() {
    println!("{:?}", item);
}
nnnmmm
  • 7,964
  • 4
  • 22
  • 41
  • I don’t need O(1) random access in this case, but the asymptotic heap allocation complexity is what I’m looking for! This is O(1) heap allocations (2 allocations for each list) vs. O(n) heap allocations (an allocation for each item). – Calebmer Mar 20 '20 at 15:58