Skip to content

Instantly share code, notes, and snippets.

@dobkeratops
Last active August 29, 2015 13:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dobkeratops/9841737 to your computer and use it in GitHub Desktop.
Save dobkeratops/9841737 to your computer and use it in GitHub Desktop.
internal vtable / c++ class emulation , again
#[feature(macro_rules)];
#[allow(macro_rules)];
use std::{io,mem,raw,cast};
// emulating C++ classes using Traits as vtables, could the safety be improved?
pub trait Foo {
fn foo(&self,i:int);
}
pub struct CppClass<RawDataStruct,TraitInterface> {
priv vtable_ptr: *TraitInterface, // you must not change this :(
data:RawDataStruct
}
type Interface<T> =CppClass<(), T>;
impl<Struct,Trait> CppClass<Struct,Trait> {
pub fn as_trait_obj<'a>(&'a self)->Trait {
unsafe {
cast::transmute::<_,Trait>( (self.vtable_ptr, &self.data) )
}
}
}
impl<Struct,Trait> Deref<Trait> for CppClass<Struct,Trait> {
fn deref<'a>(&'a self)->&'a Trait {
unsafe {
// the temporary 'trait object' lives long enough within a
// deref expression - its just pointers from the object
// its' formed from
cast::transmute(&self.as_trait_obj())
}
}
}
macro_rules! new_class {
($tname:ty for $sname:ident { $($field:ident : $val:expr),* })=>{
// todo: we can't make initializer list work for arbitrary type,
// so add another rule to accept a constructor function for non ident type.
CppClass::<$sname,$tname> {
vtable_ptr:unsafe {
let fake_obj:&'static $sname=cast::transmute(0);
let (vt,_):(*u8,*u8)=
cast::transmute(fake_obj as &'static $tname);
cast::transmute(vt)
},
data: $sname { $($field: $val),* }
}
}
}
struct Banana { x:int }
impl Foo for Banana {
fn foo(&self,i:int) {
println!("hello from banana.foo(i)={:?} self.x={:?} i+self.x={:?}",i, self.x, i+self.x);
}
}
fn main() {
let mut b:CppClass<Banana,&'static Foo> = new_class!( Foo for Banana {x:10} );
b.data.x=20;
b.foo(3);
// convince ourselves we really do have a compact internal vtable..
println!("size of CppClass={}",mem::size_of_val(&b));
println!("size of data only={}",mem::size_of_val(&b.data));
println!("size of vtable ptr={}",mem::size_of_val(&b.vtable_ptr));
println!("size of any ptr={}",mem::size_of_val(&(&b)));
do_with_foo(unsafe{cast::transmute(&b)});
}
// Take an object with Foo interface but no knowledge of underlying type.
type IFoo=CppClass<(),&'static Foo>;
fn do_with_foo(f: &Interface<&'static Foo>) {
f.foo(4);
}
@dobkeratops
Copy link
Author

many variations possible...

  • could be 'Banana as Foo' for more symetry with the whole trait-object idea.
  • if its' deemed you'd use the vtable calls more, deref() could return the trait object, and you just have an accessor '.data() to get the data members.
    etc.
  • or you could make a macro 'vcall!(b.foo(3))' ..

Imagine a typesafe language extention to extract the raw vtable (knowing what struct/impl it is), and recompose it into a temporary fatpointer. Implement various variations of object systems, eg compact vtable lookup from a u8 discriminant .. class-objects holding many instances.. whatever.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment