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()
.
Dynamic dispatch. Rust provides dynamic dispatch through a feature called 'trait objects'. Trait objects, like &Foo or Box<Foo> , are normal values that store a value of any type that implements the given trait, where the precise type can only be known at runtime.
A trait object is an opaque value of another type that implements a set of traits. The set of traits is made up of an object safe base trait plus any number of auto traits. Trait objects implement the base trait, its auto traits, and any supertraits of the base trait.
Object Safety A trait is object safe if it has the following qualities (defined in RFC 255): All supertraits must also be object safe. Sized must not be a supertrait. In other words, it must not require Self: Sized . It must not have any associated constants.
dyn is a prefix of a trait object's type. The dyn keyword is used to highlight that calls to methods on the associated Trait are dynamically dispatched. To use the trait this way, it must be 'object safe'. Unlike generic parameters or impl Trait , the compiler does not know the concrete type that is being passed.
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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With