Unpacked trait objects


#1

Generic summary: I want to do somewhat, found a technique which is inconvenient, and want to make sure no one knows a more convenient technique before i file an RFC. Know a better way to do the following?

EDIT: Now as i think of it, i could potentially try a compiler plugin rather than an RFC, which raises the question too whether we think this is worthy to include in the stock compiler. Nonetheless, my earlier question remains.

I have a parametric struct, all whose members are essentially pointers of some kinds (e.g. Arc, Vec), for example:

struct T<A: ?Sized, B> {
    x: Arc<A>,
    y: Vec<B>,
}

and want an array of these heterogeneous in the type parametres only, with the struct itself and the vtable pointer (or better yet, the vtable itself) all stored together contiguously. This is possible in unsafe Rust:

struct T {
    x: *mut (),
    y: Vec<()>,
    method1: fn(&T, x: A1, ...) -> Z1,
    method2: fn(T, x: A2, ...) -> Z2,
    drop: fn(&mut T),
}

impl T {
    fn method1(&self, x: A1, ...) -> Z1 { (self.method1)(self, x, ...) }
    fn method2(&self, x: A2, ...) -> Z2 { (self.method2)(self, x, ...) }
}

impl Drop for T {
    fn drop(&mut self) { (self.drop)(self) }
}

struct ActualT<A: ?Sized, B> {
    x: Arc<A>,
    y: Vec<B>,
    method1: fn(&ActualT<A, B>, x: A1, ...) -> Z1,
    method2: fn(ActualT<A, B>, x: A2, ...) -> Z2,
    drop: fn(&mut ActualT<A, B>),
}

impl<A: ?Sized, B> ActualT<A, B> {
    fn method1(&self, x: A1, ...) -> Z1 { _ }
    fn method2(self, x: A2, ...) -> Z2 { _ }
    fn do_drop(&mut self) { core::mem::drop(self) }
}

fn mkT() -> T {
    type AT = ActualT<SomeA, SomeB>;
    core::mem::transmute::<AT, T>(ActualT {
        x: _,
        y: _,
        method1: AT::method1,
        method2: AT::method2,
        drop: AT::do_drop,
    })
}

which essentially unpacks the struct we actually want and the vtable into a new struct, which has no parametres and can be held in a Vec or whatever. But this is quite verbose and cumbersome, and i feel rustc ought to be able to generate this code for me, lest Rust fall short of its pledge of zero-cost abstractions, and so i needn’t write unsafe code every time.


#2

Note that unless you pass an object safe trait as the type parameter A there will be not vtables in this code and it will all be statically dispatched. Specifically parameterizing the struct with a trait to create a trait object is the way that rust supports type erasure to allow a collection with heterogeneous concrete types. It seems like you want something else?