extern crate libc; | |
use libc::types::common::c99::*; | |
use libc::types::os::arch::c95::*; | |
#[repr(C)] | |
pub struct ObjInternal { | |
refcount: size_t, | |
class: *mut ObjInternal, | |
} | |
#[link(name = "cfish")] | |
extern { | |
static CFISH_OBJ: *mut ObjInternal; | |
static CFISH_CLASS: *mut ObjInternal; | |
static CFISH_STRING: *mut ObjInternal; | |
fn cfish_bootstrap_parcel(); | |
fn cfish_inc_refcount(obj: *mut ObjInternal); | |
fn cfish_dec_refcount(obj: *mut ObjInternal) -> uint32_t; | |
// The libc crate doesn't support the C99 _Bool type. | |
// See https://github.com/rust-lang/rust/issues/14608 | |
fn cfish_Obj_is_a(obj: *mut ObjInternal, class: *mut ObjInternal) | |
-> c_char; | |
fn cfish_Str_new_from_trusted_utf8(utf8: *const u8, size: size_t) | |
-> *mut ObjInternal; | |
static CFISH_Str_Get_Size_OFFSET: size_t; | |
} | |
// Obj | |
pub struct Obj { | |
ptr: *mut ObjInternal, | |
} | |
#[allow(non_snake_case)] | |
pub trait ObjTrait { | |
fn PTR(&self) -> *mut ObjInternal; | |
} | |
#[allow(non_snake_case)] | |
pub trait ObjCore { | |
fn CLASS() -> Class; | |
fn NEW_FROM_PTR(ptr: *mut ObjInternal) -> Self; | |
} | |
pub trait ObjCopy { | |
fn copy(&self) -> Self; | |
} | |
impl<T:ObjTrait + ObjCore> ObjCopy for T { | |
fn copy(&self) -> T { | |
T::NEW_FROM_PTR(self.PTR()) | |
} | |
} | |
impl ObjTrait for Obj { | |
fn PTR(&self) -> *mut ObjInternal { self.ptr } | |
} | |
impl ObjCore for Obj { | |
fn CLASS() -> Class { Class { ptr: CFISH_OBJ } } | |
fn NEW_FROM_PTR(ptr: *mut ObjInternal) -> Self { | |
unsafe { cfish_inc_refcount(ptr); } | |
Obj { ptr: ptr } | |
} | |
} | |
impl Drop for Obj { | |
fn drop(&mut self) { | |
unsafe { cfish_dec_refcount(self.ptr); } | |
} | |
} | |
impl Clone for Obj { | |
fn clone(&self) -> Obj { | |
// Should invoke Obj_Clone. | |
unsafe { cfish_inc_refcount(self.ptr); } | |
Obj { ptr: self.ptr } | |
} | |
} | |
pub fn cfish_class<T: ObjCore>() -> Class { T::CLASS() } | |
pub fn is_a<T: ObjCore>(obj: &ObjTrait) -> bool { | |
unsafe { return cfish_Obj_is_a(obj.PTR(), T::CLASS().PTR()) != 0; } | |
} | |
pub fn cast<T: ObjCore>(obj: &ObjTrait) -> Option<T> { | |
if is_a::<T>(obj) { | |
return Some(T::NEW_FROM_PTR(obj.PTR())); | |
} | |
else { | |
return None; | |
} | |
} | |
// Class (no need for refcount handling) | |
pub struct Class { | |
ptr: *mut ObjInternal, | |
} | |
impl ObjTrait for Class { | |
fn PTR(&self) -> *mut ObjInternal { self.ptr } | |
} | |
impl ObjCore for Class { | |
fn CLASS() -> Class { Class { ptr: CFISH_CLASS } } | |
fn NEW_FROM_PTR(ptr: *mut ObjInternal) -> Self { Class { ptr: ptr } } | |
} | |
// String | |
pub struct String { | |
ptr: *mut ObjInternal, | |
} | |
impl ObjTrait for String { | |
fn PTR(&self) -> *mut ObjInternal { self.ptr } | |
} | |
impl ObjCore for String { | |
fn CLASS() -> Class { Class { ptr: CFISH_STRING } } | |
fn NEW_FROM_PTR(ptr: *mut ObjInternal) -> Self { | |
unsafe { cfish_inc_refcount(ptr); } | |
String { ptr: ptr } | |
} | |
} | |
pub trait StringTrait : ObjTrait { | |
fn get_size(&self) -> usize { | |
let ptr = self.PTR(); | |
unsafe { | |
let class = (*ptr).class as *const c_char; | |
let method_ptr = class.offset(CFISH_Str_Get_Size_OFFSET as isize) | |
as *const extern fn (*mut ObjInternal) -> size_t; | |
return (*method_ptr)(ptr) as usize; | |
} | |
} | |
} | |
impl StringTrait for String {} | |
impl Drop for String { | |
fn drop(&mut self) { | |
unsafe { cfish_dec_refcount(self.ptr); } | |
} | |
} | |
impl Clone for String { | |
fn clone(&self) -> String { | |
unsafe { cfish_inc_refcount(self.ptr); } | |
String { ptr: self.ptr } | |
} | |
fn clone_from(&mut self, source: &String) { | |
unsafe { cfish_inc_refcount(self.ptr); } | |
self.ptr = source.ptr; | |
} | |
} | |
impl String { | |
fn new(s: &str) -> String { | |
unsafe { | |
return String { | |
ptr: cfish_Str_new_from_trusted_utf8(s.as_ptr(), | |
s.len() as size_t), | |
} | |
} | |
} | |
} | |
// main | |
fn main() { | |
unsafe { cfish_bootstrap_parcel(); } | |
// Create a new String (Clownfish String, not Rust String). | |
let string: String = String::new("Hello, world!"); | |
{ | |
// Create String trait object. | |
let string_trait: &StringTrait = &string; | |
// Create Obj trait object. | |
let obj_trait: &ObjTrait = &string; | |
// Unfortunately, Rust doesn't allow to upcast trait objects. | |
//let new_obj_trait: &ObjTrait = string_trait; | |
// Obj trait knows it's a String. | |
assert!(is_a::<String>(obj_trait)); | |
// String is not a Class. | |
assert!(!is_a::<Class>(&string)); | |
// Method call works. | |
assert_eq!(string.get_size(), 13); | |
} | |
// We can't add the Copy trait to Clownfish objects, because we must | |
// call incref when creating a copy. Unfortunately, Rust doesn't allow | |
// to override the behavior of the Copy trait. This means that | |
// Clownfish object structs have "move" semantics. | |
// This invalidates the original string. This only works because the | |
// references we created earlier are out of scope. | |
let string_copy = string; | |
// This would fail now, for example. | |
//let string_ref: &String = &string; | |
// Custom "copy" method that creates a new String struct pointing | |
// to the original string, leaving the original struct intact. | |
let another_copy: String = string_copy.copy(); | |
// Upcast String to Object. It's easier to cast to an ObjTrait | |
// implicitly. | |
let obj: Obj = cast::<Obj>(&string_copy).expect("Invalid cast"); | |
assert_eq!(obj.PTR(), string_copy.PTR()); | |
// Downcast Obj back to String. | |
let cast_string: String = cast::<String>(&obj).expect("Invalid cast"); | |
// Invalid cast of String to Class. | |
let option: Option<Class> = cast::<Class>(&string_copy); | |
assert!(option.is_none()); | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment