Typesystem global instance churn, again
I don't know if I'm ever going to get this right. It's a massive pain having to pass around the base "Method" type everywhere. It really makes a lot more sense to have it already defined someplace statically available. It makes doing like getting an attribute or vtable entry a lot more ergonomic. Previously we'd have to pass in the Method type every time, which was silly. Now we can just let the MethodInst::instantiate() function query it directly. Like, this is 100000% better. Also, I got rid of get_attr_lazy in favor of get_vtable_attr. I think that I want to unify get_attr and get_vtable_attr, but that would require a GC pointer to the "self" object on every object that you create. That's a bit iffy. But for now, things are feeling a little better and all the tests are passing, so that's good at least. Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
251
src/obj.rs
251
src/obj.rs
@@ -6,6 +6,7 @@ mod macros;
|
||||
pub mod function;
|
||||
|
||||
use std::any::Any;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{self, Debug, Display};
|
||||
use std::rc::Rc;
|
||||
@@ -14,7 +15,7 @@ use gc::{Finalize, Gc, GcCell, Trace};
|
||||
|
||||
use crate::obj::function::*;
|
||||
use crate::obj::macros::*;
|
||||
use crate::vm::{Argc, Chunk, Vm};
|
||||
use crate::vm::{Argc, Vm};
|
||||
|
||||
pub type Ptr<T> = Gc<GcCell<T>>;
|
||||
pub type ObjP = Ptr<dyn Obj>;
|
||||
@@ -71,7 +72,11 @@ pub fn upcast_obj<T: Obj>(ptr: Ptr<T>) -> ObjP {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_types(builtins: &mut HashMap<String, ObjP>) {
|
||||
thread_local! {
|
||||
pub static BUILTINS: RefCell<HashMap<String, ObjP>> = RefCell::new(HashMap::default());
|
||||
}
|
||||
|
||||
pub fn init_types() {
|
||||
#![allow(non_snake_case)]
|
||||
macro_rules! types {
|
||||
(
|
||||
@@ -84,6 +89,7 @@ pub fn init_types(builtins: &mut HashMap<String, ObjP>) {
|
||||
) => {{
|
||||
$(
|
||||
let $name = make_ptr(TypeInst::new(stringify!($name)));
|
||||
BUILTINS.with_borrow_mut(|builtins| builtins.insert(stringify!($name).to_string(), $name.clone()));
|
||||
)*
|
||||
|
||||
// We have to instantiate these objects all by hand. This is because the `instantiate`
|
||||
@@ -93,13 +99,8 @@ pub fn init_types(builtins: &mut HashMap<String, ObjP>) {
|
||||
let base_type = $base_type.clone();
|
||||
$name.borrow_mut().set_attr("__type__", base_type);
|
||||
with_obj_downcast_mut($name.clone(), |type_inst: &mut TypeInst| { type_inst.base.is_instantiated = true; });
|
||||
//$name.borrow_mut().base.is_instantiated = true;
|
||||
})*
|
||||
|
||||
$(
|
||||
builtins.insert(stringify!($name).to_string(), $name.clone());
|
||||
)*
|
||||
|
||||
$({
|
||||
$(
|
||||
let vtable_name = stringify!($vtable_name);
|
||||
@@ -118,49 +119,49 @@ pub fn init_types(builtins: &mut HashMap<String, ObjP>) {
|
||||
// type definitions
|
||||
Type {
|
||||
// Method conversion
|
||||
to_str => builtins.create_builtin_function("to_str", BaseObjInst::to_str, 1),
|
||||
to_repr => builtins.create_builtin_function("to_repr", BaseObjInst::to_repr, 1),
|
||||
to_bool => builtins.create_builtin_function("to_bool", BaseObjInst::to_bool, 1),
|
||||
len => builtins.create_builtin_function("len", BaseObjInst::not_implemented_un, 1),
|
||||
to_str => BuiltinFunctionInst::create("to_str", BaseObjInst::to_str, 1),
|
||||
to_repr => BuiltinFunctionInst::create("to_repr", BaseObjInst::to_repr, 1),
|
||||
to_bool => BuiltinFunctionInst::create("to_bool", BaseObjInst::to_bool, 1),
|
||||
len => BuiltinFunctionInst::create("len", BaseObjInst::not_implemented_un, 1),
|
||||
// Operators
|
||||
__add__ => builtins.create_builtin_function("__add__", BaseObjInst::not_implemented_bin, 2),
|
||||
__sub__ => builtins.create_builtin_function("__sub__", BaseObjInst::not_implemented_bin, 2),
|
||||
__mul__ => builtins.create_builtin_function("__mul__", BaseObjInst::not_implemented_bin, 2),
|
||||
__div__ => builtins.create_builtin_function("__div__", BaseObjInst::not_implemented_bin, 2),
|
||||
__and__ => builtins.create_builtin_function("__and__", BaseObjInst::and, 2),
|
||||
__or__ => builtins.create_builtin_function("__or__", BaseObjInst::or, 2),
|
||||
__ne__ => builtins.create_builtin_function("__ne__", BaseObjInst::ne, 2),
|
||||
__eq__ => builtins.create_builtin_function("__eq__", BaseObjInst::eq, 2),
|
||||
__gt__ => builtins.create_builtin_function("__gt__", BaseObjInst::not_implemented_bin, 2),
|
||||
__ge__ => builtins.create_builtin_function("__ge__", BaseObjInst::not_implemented_bin, 2),
|
||||
__lt__ => builtins.create_builtin_function("__lt__", BaseObjInst::not_implemented_bin, 2),
|
||||
__le__ => builtins.create_builtin_function("__le__", BaseObjInst::not_implemented_bin, 2),
|
||||
__pos__ => builtins.create_builtin_function("__pos__", BaseObjInst::not_implemented_un, 1),
|
||||
__neg__ => builtins.create_builtin_function("__neg__", BaseObjInst::not_implemented_un, 1),
|
||||
__not__ => builtins.create_builtin_function("__not__", BaseObjInst::not, 1),
|
||||
__add__ => BuiltinFunctionInst::create("__add__", BaseObjInst::not_implemented_bin, 2),
|
||||
__sub__ => BuiltinFunctionInst::create("__sub__", BaseObjInst::not_implemented_bin, 2),
|
||||
__mul__ => BuiltinFunctionInst::create("__mul__", BaseObjInst::not_implemented_bin, 2),
|
||||
__div__ => BuiltinFunctionInst::create("__div__", BaseObjInst::not_implemented_bin, 2),
|
||||
__and__ => BuiltinFunctionInst::create("__and__", BaseObjInst::and, 2),
|
||||
__or__ => BuiltinFunctionInst::create("__or__", BaseObjInst::or, 2),
|
||||
__ne__ => BuiltinFunctionInst::create("__ne__", BaseObjInst::ne, 2),
|
||||
__eq__ => BuiltinFunctionInst::create("__eq__", BaseObjInst::eq, 2),
|
||||
__gt__ => BuiltinFunctionInst::create("__gt__", BaseObjInst::not_implemented_bin, 2),
|
||||
__ge__ => BuiltinFunctionInst::create("__ge__", BaseObjInst::not_implemented_bin, 2),
|
||||
__lt__ => BuiltinFunctionInst::create("__lt__", BaseObjInst::not_implemented_bin, 2),
|
||||
__le__ => BuiltinFunctionInst::create("__le__", BaseObjInst::not_implemented_bin, 2),
|
||||
__pos__ => BuiltinFunctionInst::create("__pos__", BaseObjInst::not_implemented_un, 1),
|
||||
__neg__ => BuiltinFunctionInst::create("__neg__", BaseObjInst::not_implemented_un, 1),
|
||||
__not__ => BuiltinFunctionInst::create("__not__", BaseObjInst::not, 1),
|
||||
},
|
||||
Obj { },
|
||||
Str {
|
||||
to_str => builtins.create_builtin_function("to_str", StrInst::to_str, 1),
|
||||
to_repr => builtins.create_builtin_function("to_repr", StrInst::to_repr, 1),
|
||||
len => builtins.create_builtin_function("len", StrInst::len, 1),
|
||||
to_str => BuiltinFunctionInst::create("to_str", StrInst::to_str, 1),
|
||||
to_repr => BuiltinFunctionInst::create("to_repr", StrInst::to_repr, 1),
|
||||
len => BuiltinFunctionInst::create("len", StrInst::len, 1),
|
||||
// Operators
|
||||
__add__ => builtins.create_builtin_function("__add__", StrInst::add, 2),
|
||||
__mul__ => builtins.create_builtin_function("__mul__", StrInst::mul, 2),
|
||||
__add__ => BuiltinFunctionInst::create("__add__", StrInst::add, 2),
|
||||
__mul__ => BuiltinFunctionInst::create("__mul__", StrInst::mul, 2),
|
||||
// .lower, .upper, .slice, etc
|
||||
},
|
||||
Int {
|
||||
// Operators
|
||||
__add__ => builtins.create_builtin_function("__add__", IntInst::add, 2),
|
||||
__sub__ => builtins.create_builtin_function("__sub__", IntInst::sub, 2),
|
||||
__mul__ => builtins.create_builtin_function("__mul__", IntInst::mul, 2),
|
||||
__div__ => builtins.create_builtin_function("__div__", IntInst::div, 2),
|
||||
__gt__ => builtins.create_builtin_function("__gt__", IntInst::gt, 2),
|
||||
__ge__ => builtins.create_builtin_function("__ge__", IntInst::ge, 2),
|
||||
__lt__ => builtins.create_builtin_function("__lt__", IntInst::lt, 2),
|
||||
__le__ => builtins.create_builtin_function("__le__", IntInst::le, 2),
|
||||
__pos__ => builtins.create_builtin_function("__pos__", IntInst::pos, 1),
|
||||
__neg__ => builtins.create_builtin_function("__neg__", IntInst::neg, 1),
|
||||
__add__ => BuiltinFunctionInst::create("__add__", IntInst::add, 2),
|
||||
__sub__ => BuiltinFunctionInst::create("__sub__", IntInst::sub, 2),
|
||||
__mul__ => BuiltinFunctionInst::create("__mul__", IntInst::mul, 2),
|
||||
__div__ => BuiltinFunctionInst::create("__div__", IntInst::div, 2),
|
||||
__gt__ => BuiltinFunctionInst::create("__gt__", IntInst::gt, 2),
|
||||
__ge__ => BuiltinFunctionInst::create("__ge__", IntInst::ge, 2),
|
||||
__lt__ => BuiltinFunctionInst::create("__lt__", IntInst::lt, 2),
|
||||
__le__ => BuiltinFunctionInst::create("__le__", IntInst::le, 2),
|
||||
__pos__ => BuiltinFunctionInst::create("__pos__", IntInst::pos, 1),
|
||||
__neg__ => BuiltinFunctionInst::create("__neg__", IntInst::neg, 1),
|
||||
},
|
||||
Float { },
|
||||
Bool { },
|
||||
@@ -171,74 +172,6 @@ pub fn init_types(builtins: &mut HashMap<String, ObjP>) {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ObjFactory {
|
||||
fn builtins(&self) -> &HashMap<String, ObjP>;
|
||||
|
||||
fn create_obj(&self) -> ObjP {
|
||||
ObjInst::create(self.builtins().get("Obj").unwrap().clone())
|
||||
}
|
||||
|
||||
fn create_type(&self, name: impl ToString) -> ObjP {
|
||||
TypeInst::create(self.builtins().get("Type").unwrap().clone(), name)
|
||||
}
|
||||
|
||||
fn create_str(&self, str_value: impl ToString) -> ObjP {
|
||||
StrInst::create(self.builtins().get("Str").unwrap().clone(), str_value)
|
||||
}
|
||||
|
||||
fn create_int(&self, int_value: i64) -> ObjP {
|
||||
IntInst::create(self.builtins().get("Int").unwrap().clone(), int_value)
|
||||
}
|
||||
|
||||
fn create_float(&self, float_value: f64) -> ObjP {
|
||||
FloatInst::create(self.builtins().get("Float").unwrap().clone(), float_value)
|
||||
}
|
||||
|
||||
fn create_bool(&self, bool_value: bool) -> ObjP {
|
||||
BoolInst::create(self.builtins().get("Bool").unwrap().clone(), bool_value)
|
||||
}
|
||||
|
||||
fn create_nil(&self) -> ObjP {
|
||||
NilInst::create(self.builtins().get("Nil").unwrap().clone())
|
||||
}
|
||||
|
||||
fn create_builtin_function(
|
||||
&self,
|
||||
name: impl ToString,
|
||||
function: BuiltinFunctionPtr,
|
||||
arity: Argc,
|
||||
) -> ObjP {
|
||||
BuiltinFunctionInst::create(
|
||||
self.builtins().get("BuiltinFunction").unwrap().clone(),
|
||||
name,
|
||||
function,
|
||||
arity,
|
||||
)
|
||||
}
|
||||
|
||||
fn create_user_function(&self, chunk: Chunk, arity: Argc) -> ObjP {
|
||||
UserFunctionInst::create(
|
||||
self.builtins().get("UserFunction").unwrap().clone(),
|
||||
chunk,
|
||||
arity,
|
||||
)
|
||||
}
|
||||
|
||||
fn create_method(&self, self_binding: ObjP, function: ObjP) -> ObjP {
|
||||
MethodInst::create(
|
||||
self.builtins().get("Method").unwrap().clone(),
|
||||
self_binding,
|
||||
function,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjFactory for HashMap<String, ObjP> {
|
||||
fn builtins(&self) -> &Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience function for creating pointers, in case the `Arc<RwLock<T>>` pointer type has to
|
||||
/// change.
|
||||
///
|
||||
@@ -253,7 +186,7 @@ pub fn make_ptr<T: Obj>(obj: T) -> ObjP {
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
pub trait Obj: Debug + Display + Any + Trace {
|
||||
fn instantiate(&mut self, ty: ObjP);
|
||||
fn instantiate(&mut self);
|
||||
|
||||
fn is_instantiated(&self) -> bool;
|
||||
fn attrs(&self) -> &Attrs;
|
||||
@@ -264,21 +197,12 @@ pub trait Obj: Debug + Display + Any + Trace {
|
||||
}
|
||||
|
||||
fn get_attr(&self, name: &str) -> Option<ObjP> {
|
||||
// check attrs, then check vtable
|
||||
self.attrs().get(name).map(Ptr::clone)
|
||||
self.attrs().get(name).map(ObjP::clone)
|
||||
}
|
||||
|
||||
/// This tries to get an attribute using these sources:
|
||||
/// * `self.attrs()`
|
||||
/// * `self.type_inst().vtable`
|
||||
/// * `self.type_inst().type_inst().vtable` - our type's type's vtable (search up the type
|
||||
/// inheritance tree)
|
||||
///
|
||||
/// If the value is found in a vtable, then it is inserted as an attribute. If it is a
|
||||
/// function (BuiltinFunctionInst, UserFunctionInst), then it is wrapped in a `MethodInst`
|
||||
/// first.
|
||||
fn get_attr_lazy(&mut self, self_ptr: ObjP, method_ty: ObjP, name: &str) -> Option<ObjP> {
|
||||
let attr = self.get_attr(name);
|
||||
fn get_vtable_attr(&self, self_ptr: ObjP, name: &str) -> Option<ObjP> {
|
||||
// check attrs, then check vtable
|
||||
let attr = self.attrs().get(name).map(ObjP::clone);
|
||||
if attr.is_some() {
|
||||
return attr;
|
||||
}
|
||||
@@ -293,11 +217,14 @@ pub trait Obj: Debug + Display + Any + Trace {
|
||||
let ptr = if obj_is_inst::<BuiltinFunctionInst>(&vtable_entry)
|
||||
|| obj_is_inst::<UserFunctionInst>(&vtable_entry)
|
||||
{
|
||||
MethodInst::create(method_ty.clone(), self_ptr.clone(), vtable_entry)
|
||||
MethodInst::create(self_ptr.clone(), vtable_entry)
|
||||
} else {
|
||||
vtable_entry
|
||||
};
|
||||
self.set_attr(name, ptr.clone());
|
||||
// TODO Obj::get_attr - cache the vtable result somehow? we aren't caching for
|
||||
// speed, but rather so we don't have a million different method objects
|
||||
// floating around.
|
||||
//self.set_attr(name, ptr.clone());
|
||||
ptr
|
||||
});
|
||||
if vtable_entry.is_some() {
|
||||
@@ -311,9 +238,7 @@ pub trait Obj: Debug + Display + Any + Trace {
|
||||
}
|
||||
}
|
||||
|
||||
fn type_inst(&self) -> ObjP {
|
||||
self.get_attr("__type__").unwrap()
|
||||
}
|
||||
fn type_inst(&self) -> ObjP;
|
||||
|
||||
fn type_name(&self) -> Rc<String> {
|
||||
with_obj_downcast(self.type_inst(), |type_inst: &TypeInst| {
|
||||
@@ -372,8 +297,7 @@ impl Display for BaseObjInst {
|
||||
}
|
||||
|
||||
impl Obj for BaseObjInst {
|
||||
fn instantiate(&mut self, ty: ObjP) {
|
||||
self.set_attr("__type__", ty);
|
||||
fn instantiate(&mut self) {
|
||||
self.is_instantiated = true;
|
||||
}
|
||||
|
||||
@@ -389,6 +313,10 @@ impl Obj for BaseObjInst {
|
||||
&mut self.attrs
|
||||
}
|
||||
|
||||
fn type_inst(&self) -> ObjP {
|
||||
self.get_attr("__type__").expect("no __type__")
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn Obj) -> bool {
|
||||
if let Some(other) = other.as_any().downcast_ref::<BaseObjInst>() {
|
||||
// compare all attrs
|
||||
@@ -451,7 +379,7 @@ impl Obj for ObjInst {
|
||||
}
|
||||
}
|
||||
|
||||
impl_base_obj!();
|
||||
impl_base_obj!(Obj);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -523,7 +451,7 @@ impl Obj for TypeInst {
|
||||
}
|
||||
}
|
||||
|
||||
impl_base_obj!();
|
||||
impl_base_obj!(Type);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -575,7 +503,7 @@ impl Obj for StrInst {
|
||||
}
|
||||
}
|
||||
|
||||
impl_base_obj!();
|
||||
impl_base_obj!(Str);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -628,7 +556,7 @@ impl Obj for IntInst {
|
||||
}
|
||||
}
|
||||
|
||||
impl_base_obj!();
|
||||
impl_base_obj!(Int);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -681,7 +609,7 @@ impl Obj for FloatInst {
|
||||
}
|
||||
}
|
||||
|
||||
impl_base_obj!();
|
||||
impl_base_obj!(Float);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -732,7 +660,7 @@ impl Obj for BoolInst {
|
||||
}
|
||||
}
|
||||
|
||||
impl_base_obj!();
|
||||
impl_base_obj!(Bool);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -771,7 +699,7 @@ impl Obj for NilInst {
|
||||
other.as_any().downcast_ref::<NilInst>().is_some()
|
||||
}
|
||||
|
||||
impl_base_obj!();
|
||||
impl_base_obj!(Nil);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -780,53 +708,51 @@ impl Obj for NilInst {
|
||||
|
||||
#[test]
|
||||
fn test_new_objects() {
|
||||
let mut builtins = HashMap::new();
|
||||
init_types(&mut builtins);
|
||||
init_types();
|
||||
|
||||
let type_value = builtins.create_type("Type");
|
||||
let type_value = TypeInst::create("Type");
|
||||
assert_eq!(&*type_value.borrow().type_name(), "Type");
|
||||
|
||||
let str_value = builtins.create_str("asdfasdfasdfasdfasdf");
|
||||
let str_value = StrInst::create("asdfasdfasdfasdfasdf");
|
||||
assert_eq!(&*str_value.borrow().type_name(), "Str");
|
||||
|
||||
let int_value = builtins.create_int(1234);
|
||||
let int_value = IntInst::create(1234);
|
||||
assert_eq!(&*int_value.borrow().type_name(), "Int");
|
||||
|
||||
let float_value = builtins.create_float(1234.5678);
|
||||
let float_value = FloatInst::create(1234.5678);
|
||||
assert_eq!(&*float_value.borrow().type_name(), "Float");
|
||||
|
||||
let nil_value = builtins.create_nil();
|
||||
let nil_value = NilInst::create();
|
||||
assert_eq!(&*nil_value.borrow().type_name(), "Nil");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_obj_equals() {
|
||||
let mut builtins = HashMap::new();
|
||||
init_types(&mut builtins);
|
||||
init_types();
|
||||
|
||||
let int1 = builtins.create_int(1234);
|
||||
let int2 = builtins.create_int(1234);
|
||||
let int1 = IntInst::create(1234);
|
||||
let int2 = IntInst::create(1234);
|
||||
|
||||
assert!(int1.borrow().equals(&*int2.borrow()));
|
||||
assert!(int2.borrow().equals(&*int1.borrow()));
|
||||
|
||||
let float1 = builtins.create_float(1234.0);
|
||||
let float1 = FloatInst::create(1234.0);
|
||||
assert!(int1.borrow().equals(&*float1.borrow()));
|
||||
assert!(float1.borrow().equals(&*int2.borrow()));
|
||||
|
||||
// self-equality
|
||||
let str1 = builtins.create_str("1234");
|
||||
let str1 = StrInst::create("1234");
|
||||
assert!(str1.borrow().equals(&*str1.borrow()));
|
||||
|
||||
let str2 = builtins.create_str("1234");
|
||||
let str2 = StrInst::create("1234");
|
||||
assert!(str1.borrow().equals(&*str2.borrow()));
|
||||
assert!(str2.borrow().equals(&*str1.borrow()));
|
||||
|
||||
assert!(!str1.borrow().equals(&*float1.borrow()));
|
||||
assert!(!str1.borrow().equals(&*int1.borrow()));
|
||||
|
||||
let obj1 = builtins.create_obj();
|
||||
let obj2 = builtins.create_obj();
|
||||
let obj1 = ObjInst::create();
|
||||
let obj2 = ObjInst::create();
|
||||
assert!(obj1.borrow().equals(&*obj2.borrow()));
|
||||
|
||||
// these objects aren't equal anymore
|
||||
@@ -840,15 +766,10 @@ fn test_obj_equals() {
|
||||
|
||||
#[test]
|
||||
fn test_obj_vtable() {
|
||||
let mut builtins = HashMap::new();
|
||||
init_types(&mut builtins);
|
||||
let str1 = builtins.create_str("asdfasdfasdf");
|
||||
init_types();
|
||||
let str1 = StrInst::create("asdfasdfasdf");
|
||||
|
||||
let to_string_ptr = str1.borrow_mut().get_attr_lazy(
|
||||
str1.clone(),
|
||||
builtins.get("Method").unwrap().clone(),
|
||||
"to_str",
|
||||
);
|
||||
let to_string_ptr = str1.borrow_mut().get_vtable_attr(str1.clone(), "to_str");
|
||||
assert!(to_string_ptr.is_some());
|
||||
|
||||
let to_string_ptr = to_string_ptr.unwrap();
|
||||
@@ -858,11 +779,9 @@ fn test_obj_vtable() {
|
||||
});
|
||||
|
||||
// now get the method's to_string ptr
|
||||
let method_to_string_ptr = to_string_ptr.borrow_mut().get_attr_lazy(
|
||||
to_string_ptr.clone(),
|
||||
builtins.get("Method").unwrap().clone(),
|
||||
"to_str",
|
||||
);
|
||||
let method_to_string_ptr = to_string_ptr
|
||||
.borrow_mut()
|
||||
.get_vtable_attr(to_string_ptr.clone(), "to_str");
|
||||
assert!(method_to_string_ptr.is_some());
|
||||
|
||||
// this is like doing "asdfasdfasdf".to_string().to_string()
|
||||
|
||||
Reference in New Issue
Block a user