I thought I could try more or less build a trait object from scratch without using the impl
blocks. To elaborate:
trait SomeTrait {
fn fn_1(&self);
fn fn_2(&self, a: i64);
fn fn_3(&self, a: i64, b: i64);
}
struct TraitObject {
data: *mut (),
vtable: *mut (),
}
fn dtor(this: *mut ()) {
// ...
}
fn imp_1(this: *mut ()) {
// ...
}
fn imp_2(this: *mut (), a: i64) {
// ...
}
fn imp_3(this: *mut (), a: i64, b: i64) {
// ...
}
fn main() {
let data = &... as *mut (); // something to be the object
let vtable = [dtor as *mut (),
8 as *mut (),
8 as *mut (),
imp_1 as *mut (),
imp_2 as *mut (),
imp_3 as *mut ()]; // ignore any errors in typecasting,
//this is not what I am worried about getting right
let to = TraitObject {
data: data,
vtable: vtable.as_ptr() as *mut (),
};
// again, ignore any typecast errors,
let obj: &SomeTrait = unsafe { mem::transmute(to) };
// ...
obj.fn_1();
obj.fn_2(123);
obj.fn_3(123, 456);
}
From what I understand, the order in which the member functions appear in the trait definition is not always the same as the function pointers appear in the VTable. Is there a way to determine the offsets of each of the trait methods in the VTable?
If you don't mind detecting the layout at runtime, then you can compare the function addresses at specific offsets and compare them to the addresses of a known, dummy implementation to match them up. This assumes that you know how many methods there are in the trait, since you may need to read all of them.
use std::mem;
trait SomeTrait {
fn fn_1(&self);
fn fn_2(&self, a: i64);
fn fn_3(&self, a: i64, b: i64);
}
struct Dummy;
impl SomeTrait for Dummy {
fn fn_1(&self) { unimplemented!() }
fn fn_2(&self, _a: i64) { unimplemented!() }
fn fn_3(&self, _a: i64, _b: i64) { unimplemented!() }
}
struct TraitObject {
data: *mut (),
vtable: *mut (),
}
fn main() {
unsafe {
let fn_1 = Dummy::fn_1 as *const ();
let fn_2 = Dummy::fn_2 as *const ();
let fn_3 = Dummy::fn_3 as *const ();
let dummy = &mut Dummy as &mut SomeTrait;
let dummy: TraitObject = mem::transmute(dummy);
let vtable = dummy.vtable as *const *const ();
let vtable_0 = *vtable.offset(3);
let vtable_1 = *vtable.offset(4);
let vtable_2 = *vtable.offset(5);
// Mapping vtable offsets to methods is left as an exercise to the reader. ;)
println!("{:p} {:p} {:p}", fn_1, fn_2, fn_3);
println!("{:p} {:p} {:p}", vtable_0, vtable_1, vtable_2);
}
}