You are assuming that Box and &dyn are the same, in memory.
That's not guaranteed, but I guess you may play some unsafe tricks and add some tests to at least catch the situation when it would all fall apart before you would deploy broken code to production.
You would also be able to only do that trick when Vec have cap and len match…
I would say it's more of party trick than something I would ever want to see in my code.
&[T] is a slice that references contiguous memory, but Vec<Box<dyn MyTrait>> contains heap-allocated pointers, so the Box<dyn MyTrait> objects themselves are not stored contiguously -- only the Box pointers are.
and you can't return &[T] because you have to create the slice inside your function and return a reference to it(it would result in a dangling pointer as your new [T] is dropped at the end of the func scope), there are several solutions to this, the simplest is to add another field to your struct
struct MyStruct{
things: Vec<Box<dyn MyTrait>>,
things_refs: Vec<&'a dyn MyTrait>, //keep track of the refs seperatly
}
in Vec<&'a dyn MyTrait> the references are stored contiguously, and it can be automatically dereferences to &[&dyn MyTrait]
you can update the things_regs with
self.things_refs = self.things.iter().map(|thg| thx.as_ref() as &dyn MyTrat).::<Vec<&dyn MyTrait>>collect()
Only as longer as you're able + willing to alter the inner field into a bit less "readable", somewhat more "scary looking" of an option. Box<T> is a (bit more than a) wrapper for a heap allocated std::ptr::Unique<T> with some built-in logic for de/allocation itself. The std::ptr::Unique<T> is itself a wrapper over a std::ptr::NonNull<T>, which is itself a wrapper over a bare *const T.
All of them can be (safely) kept around as &'static T for as long as you'll need your "stripped down" array of Box<T> around, provided you take care of the clean up. Implementation wise:
trait MyTrait {}
struct MyStruct {
things: Vec<&'static dyn MyTrait>
}
impl MyStruct {
// no additional overhead needed
fn get_all_things(&self) -> &[&dyn MyTrait] {
&self.things
}
fn add_thing(&mut self, thing: impl MyTrait + 'static) {
let boxed = Box::new(thing) as Box<dyn MyTrait>;
// forget about the `&mut` here: not needed for your use case
let leak = Box::leak(boxed) as &'static _;
self.things.push(leak);
}
}
impl Drop for MyStruct {
fn drop(&mut self) {
while let Some(static_ref) = self.things.pop() {
let ptr = static_ref
as *const dyn MyTrait
as *mut dyn MyTrait;
// SAFETY: all the `static_ref`s are created
// via Box::new() + Box::leak() in `MyStruct::add_thing`,
// `Box::from_raw` simply "re-captures" the original pointer back
drop(unsafe { Box::from_raw(ptr) });
}
}
}
I don't know how far he's willing to go to return that exact type but if he's willing to do with a self ref struct and also safeguard it with with a pinned return at the constructor and !Unpin, then that's the way.
Self-ref and pinning is a way, but you generally don't want to use references for that due to aliasing and other borrow-checking considerations (dangling references is not the only reason self-referencial structs are problematic in Rust). It can still be sound with references and internally unconstrained lifetimes (e.g. round-trip through a raw pointer) so long as they aren't externally exposed while unconstrained, are destroyed before moving or accessing a Box, etc.
But @00100011 demonstrated another way that doesn't require any of that.
That’s a very interesting solution, which I hadn’t considered. I’ve learned some things about the language from this, too. Thanks for taking the time to explain it.