Return &[&dyn Trait] from member Vec<Box<dyn Trait>>

I have a struct similar to this:

struct MyStruct {
    things: Vec<Box<dyn MyTrait>>
}

And I'd like to be able to do this:

fn get_all_things(&self) -> &[&dyn MyTrait] {
    how?
}

i.e. I'd like to return a slice of references to something that implements the trait rather than &[Box<dyn MyTrait>].

Is it possible?

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.

1 Like

As an alternative to unsafe with the possibility of failure (when Box<dyn Trait> and &dyn Trait have different layouts),[1] you could probably

impl<T: ?Sized + MyTrait> MyTrait for Box<T> { ... }
// or specifically
impl MyTrait for Box<dyn Trait + '_> { ... }

and then returning &[Box<dyn Trait>] would be a slice of something that implements the trait, and you could even

fn get_all_thing(&self) -> &[impl MyTrait] { ... }

if you wanted to hide the fact that you had Boxes.

Or another alternative is to return an iterator over &dyn Trait.


  1. Edit: @00100011 shows how to tackle the layout issues below ↩︎

2 Likes

&[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) });
        }
    }
}

Playground link.

3 Likes

That's a self-referencial struct which will be impractical to use (e.g. can't be moved).

1 Like

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.

1 Like

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.

1 Like