diff --git a/crates/lune-std-ffi/README.md b/crates/lune-std-ffi/README.md
index 192f298..0eca6a9 100644
--- a/crates/lune-std-ffi/README.md
+++ b/crates/lune-std-ffi/README.md
@@ -1,5 +1,9 @@
 # `lune-std-ffi`
 
+## Tests
+
+See [tests/ffi](../../tests/ffi/README.md)
+
 ## Code structure
 
 ### /c
@@ -83,16 +87,19 @@ Implememt type-casting for all CTypes
 
 ## TODO
 
-Add `CTypeInfo:add(target, from1, from2, ...)` and `:sub` `:mul` `:div` `:mod` `:pow` for math operation.
+- CString
 
-> Luau cannot handle i64 or i128
+- Add buffer for owned data support
 
-Add bor band and such bit-related operation
+- Add math operation.
 
-> Luau only supports 32bit bit operations
+  > `CTypeInfo:add(target, from1, from2, ...)` and `:sub` `:mul` `:div` `:mod` `:pow`  
+  > Luau cannot handle f64, i64 or i128, so we should provide math operation for it
 
-wchar and wstring support
+- Add bit operation
 
-string(null ending) / buffer support
+  > Luau only supports 32bit bit operations
 
-void support
+- Add wchar and wstring support
+
+  > For windows API
diff --git a/crates/lune-std-ffi/src/c/arr_info.rs b/crates/lune-std-ffi/src/c/arr_info.rs
index 4facbd2..cfd7259 100644
--- a/crates/lune-std-ffi/src/c/arr_info.rs
+++ b/crates/lune-std-ffi/src/c/arr_info.rs
@@ -140,15 +140,17 @@ impl FfiConvert for CArrInfo {
         dst: &Ref<dyn FfiData>,
         src: &Ref<dyn FfiData>,
     ) -> LuaResult<()> {
-        dst.get_inner_pointer()
-            .byte_offset(dst_offset)
-            .copy_from(src.get_inner_pointer().byte_offset(src_offset), self.get_size());
+        dst.get_inner_pointer().byte_offset(dst_offset).copy_from(
+            src.get_inner_pointer().byte_offset(src_offset),
+            self.get_size(),
+        );
         Ok(())
     }
 }
 
 impl LuaUserData for CArrInfo {
     fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
+        fields.add_meta_field(LuaMetaMethod::Type, "CArr");
         fields.add_field_method_get("size", |_, this| Ok(this.get_size()));
         fields.add_field_method_get("length", |_, this| Ok(this.get_length()));
         fields.add_field_function_get("inner", |lua, this: LuaAnyUserData| {
diff --git a/crates/lune-std-ffi/src/c/fn_info.rs b/crates/lune-std-ffi/src/c/fn_info.rs
index 62df9e8..3dd5444 100644
--- a/crates/lune-std-ffi/src/c/fn_info.rs
+++ b/crates/lune-std-ffi/src/c/fn_info.rs
@@ -1,5 +1,3 @@
-use std::ptr;
-
 use libffi::middle::{Cif, Type};
 use mlua::prelude::*;
 
@@ -155,19 +153,18 @@ impl CFnInfo {
         this: &LuaAnyUserData,
         lua_function: LuaFunction<'lua>,
     ) -> LuaResult<LuaAnyUserData<'lua>> {
-        let closure = ClosureData::new(
-            ptr::from_ref(lua),
+        let closure_data = ClosureData::alloc(
+            lua,
             self.cif.as_raw_ptr(),
             self.arg_info_list.clone(),
             self.result_info.clone(),
             lua.create_registry_value(&lua_function)?,
         )?;
-        let closure_userdata = lua.create_userdata(closure)?;
 
-        association::set(lua, CLOSURE_CFN, &closure_userdata, this)?;
-        association::set(lua, CLOSURE_FUNC, &closure_userdata, lua_function)?;
+        association::set(lua, CLOSURE_CFN, &closure_data, this)?;
+        association::set(lua, CLOSURE_FUNC, &closure_data, lua_function)?;
 
-        Ok(closure_userdata)
+        Ok(closure_data)
     }
 
     pub fn create_callable<'lua>(
@@ -206,6 +203,9 @@ impl CFnInfo {
 }
 
 impl LuaUserData for CFnInfo {
+    fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
+        fields.add_meta_field(LuaMetaMethod::Type, "CFn");
+    }
     fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
         // Subtype
         method_provider::provide_ptr(methods);
diff --git a/crates/lune-std-ffi/src/c/helper.rs b/crates/lune-std-ffi/src/c/helper.rs
index 65e5dd2..8a54fdc 100644
--- a/crates/lune-std-ffi/src/c/helper.rs
+++ b/crates/lune-std-ffi/src/c/helper.rs
@@ -302,6 +302,8 @@ pub fn stringify(lua: &Lua, userdata: &LuaAnyUserData) -> LuaResult<String> {
         CPtrInfo::stringify(lua, userdata)
     } else if userdata.is::<CFnInfo>() {
         CFnInfo::stringify(lua, userdata)
+    } else if userdata.is::<CVoidInfo>() {
+        CVoidInfo::stringify()
     } else if let Some(name) = ctype_helper::get_name(userdata)? {
         Ok(String::from(name))
     } else {
diff --git a/crates/lune-std-ffi/src/c/mod.rs b/crates/lune-std-ffi/src/c/mod.rs
index 10b9e68..6db6a69 100644
--- a/crates/lune-std-ffi/src/c/mod.rs
+++ b/crates/lune-std-ffi/src/c/mod.rs
@@ -17,7 +17,7 @@ pub use self::{
     ptr_info::CPtrInfo,
     struct_info::CStructInfo,
     type_info::{CTypeCast, CTypeInfo},
-    types::{ctype_helper, export_ctypes},
+    types::{ctype_helper, export_c_types, export_fixed_types},
     void_info::CVoidInfo,
 };
 
@@ -34,10 +34,10 @@ mod association_names {
     pub const CLOSURE_CFN: &str = "__closure_cfn";
 }
 
-pub fn export(lua: &Lua) -> LuaResult<LuaTable> {
+pub fn export_c(lua: &Lua) -> LuaResult<LuaTable> {
     TableBuilder::new(lua)?
         .with_value("void", CVoidInfo::new())?
-        .with_values(export_ctypes(lua)?)?
+        .with_values(export_c_types(lua)?)?
         .with_function("struct", |lua, types: LuaTable| {
             CStructInfo::from_table(lua, types)
         })?
diff --git a/crates/lune-std-ffi/src/c/ptr_info.rs b/crates/lune-std-ffi/src/c/ptr_info.rs
index f39b425..d66c394 100644
--- a/crates/lune-std-ffi/src/c/ptr_info.rs
+++ b/crates/lune-std-ffi/src/c/ptr_info.rs
@@ -124,6 +124,7 @@ impl CPtrInfo {
 
 impl LuaUserData for CPtrInfo {
     fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
+        fields.add_meta_field(LuaMetaMethod::Type, "CPtr");
         fields.add_field_method_get("size", |_, _| Ok(size_of::<usize>()));
         fields.add_field_function_get("inner", |lua, this| {
             let inner = association::get(lua, CPTR_INNER, this)?
diff --git a/crates/lune-std-ffi/src/c/struct_info.rs b/crates/lune-std-ffi/src/c/struct_info.rs
index 3abbe73..8917eb5 100644
--- a/crates/lune-std-ffi/src/c/struct_info.rs
+++ b/crates/lune-std-ffi/src/c/struct_info.rs
@@ -182,6 +182,7 @@ impl FfiConvert for CStructInfo {
 
 impl LuaUserData for CStructInfo {
     fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
+        fields.add_meta_field(LuaMetaMethod::Type, "CStruct");
         fields.add_field_method_get("size", |_, this| Ok(this.get_size()));
     }
     fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
diff --git a/crates/lune-std-ffi/src/c/type_info.rs b/crates/lune-std-ffi/src/c/type_info.rs
index 38bc8db..b12eebf 100644
--- a/crates/lune-std-ffi/src/c/type_info.rs
+++ b/crates/lune-std-ffi/src/c/type_info.rs
@@ -91,6 +91,7 @@ where
     Self: CTypeCast + FfiSignedness + FfiConvert + FfiSize,
 {
     fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
+        fields.add_meta_field(LuaMetaMethod::Type, "CType");
         fields.add_field_method_get("size", |_, this| Ok(this.get_size()));
         fields.add_meta_field(LuaMetaMethod::Type, "CType");
         fields.add_field_method_get("signedness", |_, this| Ok(this.get_signedness()));
diff --git a/crates/lune-std-ffi/src/c/types/mod.rs b/crates/lune-std-ffi/src/c/types/mod.rs
index 98a8893..d778874 100644
--- a/crates/lune-std-ffi/src/c/types/mod.rs
+++ b/crates/lune-std-ffi/src/c/types/mod.rs
@@ -34,7 +34,7 @@ macro_rules! create_ctypes {
         ),)*])
     };
 }
-pub fn export_ctypes(lua: &Lua) -> LuaResult<Vec<(&'static str, LuaAnyUserData)>> {
+pub fn export_c_types(lua: &Lua) -> LuaResult<Vec<(&'static str, LuaAnyUserData)>> {
     create_ctypes!(
         lua,
         // Export Compile-time known c-types
@@ -55,6 +55,11 @@ pub fn export_ctypes(lua: &Lua) -> LuaResult<Vec<(&'static str, LuaAnyUserData)>
         ("ulong", c_ulong, Type::c_ulong()),
         ("longlong", c_longlong, Type::c_longlong()),
         ("ulonglong", c_ulonglong, Type::c_ulonglong()),
+    )
+}
+pub fn export_fixed_types(lua: &Lua) -> LuaResult<Vec<(&'static str, LuaAnyUserData)>> {
+    create_ctypes!(
+        lua,
         // Export Source-time known c-types (fixed)
         ("u8", u8, Type::u8()),
         ("u16", u16, Type::u16()),
@@ -70,10 +75,8 @@ pub fn export_ctypes(lua: &Lua) -> LuaResult<Vec<(&'static str, LuaAnyUserData)>
         ("f32", f32, Type::f32()),
         ("usize", usize, Type::usize()),
         ("isize", isize, Type::isize()),
-        // TODO: c_float and c_double sometime can be half and single,
-        // TODO: but libffi-rs doesn't support it. need work-around or drop support
-        ("float", f32, Type::f32()),
-        ("double", f64, Type::f64()),
+        ("f32", f32, Type::f32()),
+        ("f64", f64, Type::f64()),
     )
 }
 
diff --git a/crates/lune-std-ffi/src/c/void_info.rs b/crates/lune-std-ffi/src/c/void_info.rs
index 5601ab0..b0008e2 100644
--- a/crates/lune-std-ffi/src/c/void_info.rs
+++ b/crates/lune-std-ffi/src/c/void_info.rs
@@ -17,6 +17,7 @@ impl FfiSize for CVoidInfo {
         0
     }
 }
+
 impl CVoidInfo {
     pub fn new() -> Self {
         Self()
@@ -24,6 +25,17 @@ impl CVoidInfo {
     pub fn get_middle_type() -> Type {
         Type::void()
     }
+    pub fn stringify() -> LuaResult<String> {
+        Ok(String::from("CVoid"))
+    }
 }
 
-impl LuaUserData for CVoidInfo {}
+impl LuaUserData for CVoidInfo {
+    fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
+        fields.add_meta_field(LuaMetaMethod::Type, "CVoid");
+    }
+    fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
+        method_provider::provide_to_string(methods);
+        method_provider::provide_ptr(methods);
+    }
+}
diff --git a/crates/lune-std-ffi/src/data/callable_data.rs b/crates/lune-std-ffi/src/data/callable_data.rs
index 51673de..780f4e2 100644
--- a/crates/lune-std-ffi/src/data/callable_data.rs
+++ b/crates/lune-std-ffi/src/data/callable_data.rs
@@ -59,6 +59,14 @@ impl CallableData {
 
             let arg_ref = arg_value.borrow::<RefData>()?;
 
+            // unsafe {
+            //     let argp = arg_ref.get_inner_pointer();
+            //     let fnr = transmute::<*mut c_void, unsafe extern "C" fn(i32, i32) -> i32>(
+            //         *argp.cast::<*mut c_void>(),
+            //     );
+            //     dbg!(fnr(1, 2));
+            // }
+
             arg_list.push(arg_ref.get_inner_pointer().cast::<c_void>());
         }
 
diff --git a/crates/lune-std-ffi/src/data/closure_data.rs b/crates/lune-std-ffi/src/data/closure_data.rs
index 7f49878..17cc71f 100644
--- a/crates/lune-std-ffi/src/data/closure_data.rs
+++ b/crates/lune-std-ffi/src/data/closure_data.rs
@@ -1,14 +1,18 @@
 use core::ffi::c_void;
-use std::{borrow::Borrow, ptr};
+use std::{borrow::Borrow, mem::transmute, ptr};
 
 use libffi::{
-    low::{closure_alloc, closure_free, ffi_cif, CodePtr},
+    low::{closure_alloc, closure_free, ffi_cif},
     raw::{ffi_closure, ffi_prep_closure_loc},
 };
 use mlua::prelude::*;
 
-use super::ref_data::{RefBounds, RefData, RefFlag};
+use super::{
+    association_names::CLSOURE_REF_INNER,
+    ref_data::{RefBounds, RefData, RefFlag, UNSIZED_BOUNDS},
+};
 use crate::ffi::{
+    association,
     libffi_helper::{ffi_status_assert, SIZE_OF_POINTER},
     FfiArg, FfiData, FfiResult,
 };
@@ -16,7 +20,7 @@ use crate::ffi::{
 pub struct ClosureData {
     lua: *const Lua,
     closure: *mut ffi_closure,
-    code: CodePtr,
+    code: *mut c_void,
     arg_info_list: Vec<FfiArg>,
     result_info: FfiResult,
     func: LuaRegistryKey,
@@ -32,6 +36,7 @@ impl Drop for ClosureData {
 
 const RESULT_REF_FLAGS: u8 =
     RefFlag::Leaked.value() | RefFlag::Writable.value() | RefFlag::Offsetable.value();
+const CLOSURE_REF_FLAGS: u8 = RefFlag::Function.value();
 
 unsafe extern "C" fn callback(
     cif: *mut ffi_cif,
@@ -45,7 +50,7 @@ unsafe extern "C" fn callback(
     let len = (*cif).nargs as usize;
     let mut args = Vec::<LuaValue>::with_capacity(len + 1);
 
-    dbg!("before result");
+    dbg!("before result", closure_data.result_info.size);
 
     // Push result pointer (ref)
     args.push(LuaValue::UserData(
@@ -81,48 +86,66 @@ unsafe extern "C" fn callback(
         .unwrap()
         .as_function()
         .unwrap()
-        .call::<_, ()>(args)
+        .call::<_, ()>(LuaMultiValue::from_vec(args))
         .unwrap();
 }
 
 impl ClosureData {
-    pub fn new(
-        lua: *const Lua,
+    pub fn alloc(
+        lua: &Lua,
         cif: *mut ffi_cif,
         arg_info_list: Vec<FfiArg>,
         result_info: FfiResult,
         func: LuaRegistryKey,
-    ) -> LuaResult<ClosureData> {
+    ) -> LuaResult<LuaAnyUserData> {
         let (closure, code) = closure_alloc();
+        let code = code.as_mut_ptr();
 
-        let closure_data = ClosureData {
-            lua,
+        dbg!(result_info.size);
+
+        let closure_data = lua.create_userdata(ClosureData {
+            lua: ptr::from_ref(lua),
             closure,
             code,
             arg_info_list,
             result_info,
             func,
-        };
+        })?;
+
+        dbg!(unsafe {
+            closure_data
+                .to_pointer()
+                .cast::<ClosureData>()
+                .as_ref()
+                .unwrap()
+                .result_info
+                .size
+        });
 
         ffi_status_assert(unsafe {
             ffi_prep_closure_loc(
                 closure,
                 cif,
                 Some(callback),
-                ptr::from_ref(&closure_data).cast::<c_void>().cast_mut(),
-                code.as_mut_ptr(),
+                closure_data.to_pointer().cast_mut(),
+                code,
             )
         })?;
 
+        unsafe {
+            // let argp = closure_data.borrow::<ClosureData>()?.get_inner_pointer();
+            let fnr = transmute::<*mut c_void, unsafe extern "C" fn(i32, i32) -> i32>(code);
+            dbg!(fnr(1, 2));
+        }
+
         Ok(closure_data)
     }
 }
 
 impl FfiData for ClosureData {
     unsafe fn get_inner_pointer(&self) -> *mut () {
-        ptr::from_ref(&self.code.as_mut_ptr())
-            .cast_mut()
-            .cast::<()>()
+        ptr::from_ref(&self.code).cast_mut().cast::<()>()
+        // self.code.cast::<()>()
     }
     fn check_inner_boundary(&self, offset: isize, size: usize) -> bool {
         (offset as usize) + size <= SIZE_OF_POINTER
@@ -137,6 +160,23 @@ impl FfiData for ClosureData {
 
 impl LuaUserData for ClosureData {
     fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
-        // methods.add_function("ref", function);
+        methods.add_function("ref", |lua, this: LuaAnyUserData| {
+            let ref_data = lua.create_userdata(RefData::new(
+                unsafe { this.borrow::<ClosureData>()?.get_inner_pointer() },
+                CLOSURE_REF_FLAGS,
+                UNSIZED_BOUNDS,
+            ))?;
+            unsafe {
+                let mut b = this.borrow_mut::<ClosureData>()?;
+                b.lua = ptr::from_ref(lua);
+                let argp = b.get_inner_pointer();
+                let fnr = transmute::<*mut c_void, unsafe extern "C" fn(i32, i32) -> i32>(
+                    *argp.cast::<*mut c_void>(),
+                );
+                dbg!(fnr(1, 2));
+            }
+            association::set(lua, CLSOURE_REF_INNER, &ref_data, &this)?;
+            Ok(ref_data)
+        });
     }
 }
diff --git a/crates/lune-std-ffi/src/data/mod.rs b/crates/lune-std-ffi/src/data/mod.rs
index 1de4df1..27d3f94 100644
--- a/crates/lune-std-ffi/src/data/mod.rs
+++ b/crates/lune-std-ffi/src/data/mod.rs
@@ -14,7 +14,7 @@ pub use self::{
     callable_data::CallableData,
     closure_data::ClosureData,
     lib_data::LibData,
-    ref_data::{create_nullptr, RefBounds, RefData, RefFlag},
+    ref_data::{create_nullref, RefBounds, RefData, RefFlag},
 };
 use crate::ffi::FfiData;
 
@@ -22,6 +22,7 @@ use crate::ffi::FfiData;
 mod association_names {
     pub const REF_INNER: &str = "__ref_inner";
     pub const SYM_INNER: &str = "__syn_inner";
+    pub const CLSOURE_REF_INNER: &str = "__closure_ref_inner";
 }
 
 pub trait GetFfiData {
diff --git a/crates/lune-std-ffi/src/data/ref_data/flag.rs b/crates/lune-std-ffi/src/data/ref_data/flag.rs
index 972431d..e607e71 100644
--- a/crates/lune-std-ffi/src/data/ref_data/flag.rs
+++ b/crates/lune-std-ffi/src/data/ref_data/flag.rs
@@ -7,7 +7,6 @@ pub enum RefFlag {
     Writable,
     Offsetable,
     Function,
-    Uninit,
 }
 impl RefFlag {
     pub const fn value(&self) -> u8 {
@@ -18,7 +17,6 @@ impl RefFlag {
             Self::Readable => U8_MASK4,
             Self::Offsetable => U8_MASK5,
             Self::Function => U8_MASK6,
-            Self::Uninit => U8_MASK7,
         }
     }
 }
diff --git a/crates/lune-std-ffi/src/data/ref_data/mod.rs b/crates/lune-std-ffi/src/data/ref_data/mod.rs
index b22b840..22a123c 100644
--- a/crates/lune-std-ffi/src/data/ref_data/mod.rs
+++ b/crates/lune-std-ffi/src/data/ref_data/mod.rs
@@ -17,7 +17,6 @@ pub use self::{
 
 // Box:ref():ref() should not be able to modify, Only for external
 const BOX_REF_REF_FLAGS: u8 = 0;
-const UNINIT_REF_FLAGS: u8 = RefFlag::Uninit.value();
 // | FfiRefFlag::Writable.value()
 // | FfiRefFlag::Readable.value()
 // | FfiRefFlag::Dereferenceable.value()
@@ -45,14 +44,6 @@ impl RefData {
         }
     }
 
-    pub fn new_uninit() -> Self {
-        Self {
-            ptr: ManuallyDrop::new(Box::new(ptr::null_mut())),
-            flags: UNINIT_REF_FLAGS,
-            boundary: UNSIZED_BOUNDS,
-        }
-    }
-
     // Make FfiRef from ref
     pub fn luaref<'lua>(
         lua: &'lua Lua,
@@ -185,14 +176,11 @@ impl LuaUserData for RefData {
     }
 }
 
-pub fn create_nullptr(lua: &Lua) -> LuaResult<LuaAnyUserData> {
-    // https://en.cppreference.com/w/cpp/types/nullptr_t
+pub fn create_nullref(lua: &Lua) -> LuaResult<LuaAnyUserData> {
     lua.create_userdata(RefData::new(
         ptr::null_mut::<()>().cast(),
         0,
         // usize::MAX means that nullptr is can be 'any' pointer type
-        // We check size of inner data. give ffi.box(1):ref() as argument which typed as i32:ptr() will fail,
-        // throw lua error
         UNSIZED_BOUNDS,
     ))
 }
diff --git a/crates/lune-std-ffi/src/lib.rs b/crates/lune-std-ffi/src/lib.rs
index 6fe320e..f85a1bb 100644
--- a/crates/lune-std-ffi/src/lib.rs
+++ b/crates/lune-std-ffi/src/lib.rs
@@ -8,8 +8,8 @@ mod data;
 mod ffi;
 
 use crate::{
-    c::export as c_export,
-    data::{create_nullptr, BoxData, LibData, RefData},
+    c::{export_c, export_fixed_types},
+    data::{create_nullref, BoxData, LibData},
 };
 
 /**
@@ -21,12 +21,12 @@ use crate::{
 */
 pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
     let result = TableBuilder::new(lua)?
-        .with_function("nullRef", |lua, ()| create_nullptr(lua))?
+        .with_values(export_fixed_types(lua)?)?
+        .with_function("nullRef", |lua, ()| create_nullref(lua))?
         .with_function("box", |_lua, size: usize| Ok(BoxData::new(size)))?
         .with_function("open", |_lua, name: String| LibData::new(name))?
-        .with_function("uninitRef", |_lua, ()| Ok(RefData::new_uninit()))?
         .with_function("isInteger", |_lua, num: LuaValue| Ok(num.is_integer()))?
-        .with_value("c", c_export(lua)?)?;
+        .with_value("c", export_c(lua)?)?;
 
     #[cfg(debug_assertions)]
     let result = result.with_function("debugAssociation", |lua, str: String| {
diff --git a/tests/ffi/README.md b/tests/ffi/README.md
new file mode 100644
index 0000000..5b33949
--- /dev/null
+++ b/tests/ffi/README.md
@@ -0,0 +1,36 @@
+# tests/ffi
+
+## Requirements
+
+gcc for library compiling (for external-\*)
+
+## Results
+
+**External tests**
+
+- [x] tests/ffi/external-math
+- [x] tests/ffi/external-pointer
+- [x] tests/ffi/external-print
+- [x] tests/ffi/external-struct
+- [ ] tests/ffi/external-closure
+
+  > failed (segfault)
+
+**Luau-side**
+
+- [ ] tests/ffi/pretty-print :white_check_mark:
+
+  > need box, ref test
+
+- [x] tests/ffi/isInteger
+- [ ] tests/ffi/into-boundary
+
+  > need assertion
+
+- [ ] tests/ffi/from-boundary
+
+  > need assertion
+
+- [ ] tests/ffi/cast
+
+  > need assertion
diff --git a/tests/ffi/box-recursion-gc.luau b/tests/ffi/box-recursion-gc.luau
deleted file mode 100644
index d86d045..0000000
--- a/tests/ffi/box-recursion-gc.luau
+++ /dev/null
@@ -1,20 +0,0 @@
---!nocheck
---!nolint
-
-local ffi = require("@lune/ffi")
-
-local box = ffi.box(ffi.u8:ptr().size)
-local ref = box:ref()
-ffi.u8:ptr():into(box, ref)
-
-local wt = setmetatable({}, { __mode = "v" })
-
-wt[1] = box
-wt[2] = ref
-
-box = nil
-ref = nil
-
-collectgarbage("collect")
-
-assert(wt[1] == nil and wt[2] == nil, "Box - ref recursion GC test failed")
diff --git a/tests/ffi/external_closure/init.luau b/tests/ffi/external-closure/init.luau
similarity index 90%
rename from tests/ffi/external_closure/init.luau
rename to tests/ffi/external-closure/init.luau
index 55b38e6..a5783a7 100644
--- a/tests/ffi/external_closure/init.luau
+++ b/tests/ffi/external-closure/init.luau
@@ -1,6 +1,6 @@
 local ffi = require("@lune/ffi")
 
-local testdir = "./tests/ffi/external_closure"
+local testdir = "./tests/ffi/external-closure"
 
 local compile = require("../utility/compile")
 compile(`{testdir}/lib.c`, `{testdir}/lib.so`)
@@ -19,6 +19,7 @@ local function test_closure()
 
 	local result_box = ffi.box(ffi.c.int.size)
 	closure_test_callable(result_box, callback_closure:ref())
+	print(callback_closure)
 end
 
 test_closure()
diff --git a/tests/ffi/external_closure/lib.c b/tests/ffi/external-closure/lib.c
similarity index 54%
rename from tests/ffi/external_closure/lib.c
rename to tests/ffi/external-closure/lib.c
index e4579f4..167a2f5 100644
--- a/tests/ffi/external_closure/lib.c
+++ b/tests/ffi/external-closure/lib.c
@@ -1,12 +1,12 @@
 #include<stdio.h>
 
-typedef int (*lua_callback_t)(int a, int b);
+typedef int (*lua_callback_t)(int, int);
 
 int closure_test(lua_callback_t callback) {
     printf("%p\n", callback);
-    printf("%d\n", (*callback)(12, 24));
+    printf("%d\n", callback(12, 24));
 
-    return (*callback)(12, 24) * 2;
+    return callback(12, 24) * 2;
 }
 
 int closure(int a, int b) {
diff --git a/tests/ffi/external_math/init.luau b/tests/ffi/external-math/init.luau
similarity index 96%
rename from tests/ffi/external_math/init.luau
rename to tests/ffi/external-math/init.luau
index fe486b9..1cfd623 100644
--- a/tests/ffi/external_math/init.luau
+++ b/tests/ffi/external-math/init.luau
@@ -1,7 +1,7 @@
 local ffi = require("@lune/ffi")
 local c = ffi.c
 
-local testdir = "./tests/ffi/external_math"
+local testdir = "./tests/ffi/external-math"
 local compile = require("../utility/compile")
 compile(`{testdir}/lib.c`, `{testdir}/lib.so`)
 local lib = ffi.open(`{testdir}/lib.so`)
diff --git a/tests/ffi/external_math/lib.c b/tests/ffi/external-math/lib.c
similarity index 100%
rename from tests/ffi/external_math/lib.c
rename to tests/ffi/external-math/lib.c
diff --git a/tests/ffi/external_pointer/init.luau b/tests/ffi/external-pointer/init.luau
similarity index 91%
rename from tests/ffi/external_pointer/init.luau
rename to tests/ffi/external-pointer/init.luau
index 22ab217..57fc719 100644
--- a/tests/ffi/external_pointer/init.luau
+++ b/tests/ffi/external-pointer/init.luau
@@ -1,7 +1,7 @@
 local ffi = require("@lune/ffi")
 local c = ffi.c
 
-local testdir = "./tests/ffi/external_pointer"
+local testdir = "./tests/ffi/external-pointer"
 local compile = require("../utility/compile")
 compile(`{testdir}/lib.c`, `{testdir}/lib.so`)
 local lib = ffi.open(`{testdir}/lib.so`)
diff --git a/tests/ffi/external_pointer/lib.c b/tests/ffi/external-pointer/lib.c
similarity index 100%
rename from tests/ffi/external_pointer/lib.c
rename to tests/ffi/external-pointer/lib.c
diff --git a/tests/ffi/external_print/init.luau b/tests/ffi/external-print/init.luau
similarity index 70%
rename from tests/ffi/external_print/init.luau
rename to tests/ffi/external-print/init.luau
index 43ac11d..341481b 100644
--- a/tests/ffi/external_print/init.luau
+++ b/tests/ffi/external-print/init.luau
@@ -1,18 +1,17 @@
 local ffi = require("@lune/ffi")
+local c = ffi.c
 
-local testdir = "./tests/ffi/external_print"
-
+local testdir = "./tests/ffi/external-print"
 local compile = require("../utility/compile")
 compile(`{testdir}/lib.c`, `{testdir}/lib.so`)
-
 local lib = ffi.open(`{testdir}/lib.so`)
 
 local function test_hello_world()
-	local hello_world_info = ffi.fnInfo({}, ffi.void)
+	local hello_world_info = c.fn({}, c.void)
 
 	local hello_world_callable = hello_world_info:callable(lib:find("hello_world"))
 
-	hello_world_callable:call(nil)
+	hello_world_callable(nil)
 end
 
 test_hello_world()
diff --git a/tests/ffi/external_print/lib.c b/tests/ffi/external-print/lib.c
similarity index 100%
rename from tests/ffi/external_print/lib.c
rename to tests/ffi/external-print/lib.c
diff --git a/tests/ffi/external-struct/init.luau b/tests/ffi/external-struct/init.luau
new file mode 100644
index 0000000..0b3c829
--- /dev/null
+++ b/tests/ffi/external-struct/init.luau
@@ -0,0 +1,28 @@
+local ffi = require("@lune/ffi")
+local c = ffi.c
+
+local testdir = "./tests/ffi/external-struct"
+local compile = require("../utility/compile")
+compile(`{testdir}/lib.c`, `{testdir}/lib.so`)
+local lib = ffi.open(`{testdir}/lib.so`)
+
+local function test_AB()
+	local ArgStruct = c.struct({ c.int, c.int:ptr() })
+	local ResultStruct = c.struct({ c.int, c.int })
+
+	local AB = c.fn({ ArgStruct }, ResultStruct)
+
+	local AB_callable = AB:callable(lib:find("AB"))
+
+	local resultBox = ffi.box(ResultStruct.size)
+	local b = c.int:box(200)
+	local arg = ArgStruct:box({ 100, b:ref() })
+
+	AB_callable(resultBox, arg:ref())
+	local result = ResultStruct:readData(resultBox)
+
+	assert(result[1] == 300, `AB failed. result expected 300, got {result[1]}`)
+	assert(result[2] == 20000, `AB failed. result expected 300, got {result[2]}`)
+end
+
+test_AB()
diff --git a/tests/ffi/external_struct/lib.c b/tests/ffi/external-struct/lib.c
similarity index 100%
rename from tests/ffi/external_struct/lib.c
rename to tests/ffi/external-struct/lib.c
diff --git a/tests/ffi/external.luau b/tests/ffi/external.luau
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/ffi/external_struct/init.luau b/tests/ffi/external_struct/init.luau
deleted file mode 100644
index 76213df..0000000
--- a/tests/ffi/external_struct/init.luau
+++ /dev/null
@@ -1,30 +0,0 @@
-local ffi = require("@lune/ffi")
-
-local testdir = "./tests/ffi/external_struct"
-
-local compile = require("../utility/compile")
-compile(`{testdir}/lib.c`, `{testdir}/lib.so`)
-
-local lib = ffi.open(`{testdir}/lib.so`)
-
-local function test_AB()
-	local ArgStruct = ffi.structInfo({ ffi.int, ffi.int:ptrInfo() })
-	local ResultStruct = ffi.structInfo({ ffi.int, ffi.int })
-
-	local AB = ffi.fnInfo({ ArgStruct }, ResultStruct)
-
-	local AB_caller = AB:callable(lib:find("AB"))
-
-	local resultBox = ffi.box(ffi.int.size)
-	local a = ffi.int:box(100)
-	local b = ffi.int:box(200)
-	local arg = ArgStruct:box({ a, b:leak() })
-
-	AB_caller:call(resultBox, arg)
-	local result = ResultStruct:readData(resultBox)
-
-	assert(result[0] == 300, `AB failed. result expected 300, got {result}`)
-	assert(result[1] == 20000, `AB failed. result expected 300, got {result}`)
-end
-
-test_AB()
diff --git a/tests/ffi/pretty-print.luau b/tests/ffi/pretty-print.luau
index 7e92e32..bac478b 100644
--- a/tests/ffi/pretty-print.luau
+++ b/tests/ffi/pretty-print.luau
@@ -1,29 +1,32 @@
 local ffi = require("@lune/ffi")
+local c = ffi.c
 
-assert(typeof(ffi.int) == "CType")
-assert(tostring(ffi.int) == "int")
+assert(typeof(c.int) :: string == "CType")
+assert(tostring(c.int) == "int")
 
-assert(typeof(ffi.int:ptr()) == "CPtr")
-assert(tostring(ffi.int:ptr()) == "int")
-assert(tostring(ffi.int:arr(5):ptr()) == " <CArr( int, length = 5 )> ")
+assert(typeof(c.int:ptr()) :: string == "CPtr")
+assert(tostring(c.int:ptr()) == "int")
+assert(tostring(c.int:arr(5):ptr()) == " <CArr( int, length = 5 )> ")
 
-assert(typeof(ffi.int:arr(5)) == "CArr")
-assert(tostring(ffi.int:arr(5)) == " int, length = 5 ")
-assert(tostring(ffi.int:ptr():arr(5)) == " <CPtr(int)>, length = 5 ")
+assert(typeof(c.int:arr(5)) :: string == "CArr")
+assert(tostring(c.int:arr(5)) == " int, length = 5 ")
+assert(tostring(c.int:ptr():arr(5)) == " <CPtr(int)>, length = 5 ")
 
-assert(typeof(ffi.funcInfo({ ffi.int }, ffi.int)) == "CFunc")
-assert(tostring(ffi.funcInfo({ ffi.int }, ffi.int)) == " (int) -> int ")
-assert(tostring(ffi.funcInfo({ ffi.int, ffi.double }, ffi.int)) == " (int, double) -> int ")
-assert(tostring(ffi.funcInfo({ ffi.int:ptr() }, ffi.int)) == " (<CPtr(int)>) -> int ")
-assert(tostring(ffi.funcInfo({ ffi.int }, ffi.int:ptr())) == " (int) -> <CPtr(int)> ")
-assert(tostring(ffi.funcInfo({ ffi.int:ptr() }, ffi.int:ptr())) == " (<CPtr(int)>) -> <CPtr(int)> ")
+assert(typeof(c.fn({ c.int }, c.int)) :: string == "CFn")
+assert(tostring(c.fn({ c.int }, c.int)) == " (int) -> int ")
+assert(tostring(c.fn({ c.int, c.double }, c.int)) == " (int, double) -> int ")
+assert(tostring(c.fn({ c.int:ptr() }, c.int)) == " (<CPtr(int)>) -> int ")
+assert(tostring(c.fn({ c.int }, c.int:ptr())) == " (int) -> <CPtr(int)> ")
+assert(tostring(c.fn({ c.int:ptr() }, c.int:ptr())) == " (<CPtr(int)>) -> <CPtr(int)> ")
 assert(
-	tostring(ffi.funcInfo({ ffi.int:ptr(), ffi.int:ptr() }, ffi.int:ptr()))
+	tostring(c.fn({ c.int:ptr(), c.int:ptr() }, c.int:ptr()))
 		== " (<CPtr(int)>, <CPtr(int)>) -> <CPtr(int)> "
 )
 
-assert(typeof(ffi.structInfo({ ffi.int, ffi.char })) == "CStruct")
+assert(typeof(c.struct({ c.int, c.char })) :: string == "CStruct")
 assert(
-	tostring(ffi.structInfo({ ffi.int, ffi.char:ptr() }))
-		== ` int, <CPtr(char)>, size = {ffi.structInfo({ ffi.int, ffi.char:ptr() }).size} `
+	tostring(c.struct({ c.int, c.char:ptr() }))
+		== ` int, <CPtr(char)>, size = {c.struct({ c.int, c.char:ptr() }).size} `
 )
+
+-- FIXME: add box, ref pretty-print test
diff --git a/types/ffi.luau b/types/ffi.luau
index b6af6dd..eaa37db 100644
--- a/types/ffi.luau
+++ b/types/ffi.luau
@@ -44,7 +44,7 @@ export type CArrInfo<T, R> = {
 	writeData: (self: CArrInfo<T, R>, target: (Ref|Box), value: { T }, offset: number?) -> (),
 	copyData: (self: CArrInfo<T, R>, dst: (Ref|Box), src: (Ref|Box), dst_offset: number?, src_offset: number?) -> (),
 
-	offset: (self: CArrInfo<T, R>, offset: number) -> number,
+	offset: (self: CArrInfo<T, R>, index: number) -> number,
 }
 
 export type CFnInfo = {
@@ -53,12 +53,18 @@ export type CFnInfo = {
 }
 
 export type CStructInfo = {
+	size: number,
+
 	arr: (self: CStructInfo, len: number) -> CArrInfo<CStructInfo, {any}>,
 	ptr: (self: CStructInfo) -> CPtrInfo<CStructInfo>,
 
 	box: (self: CStructInfo, table: { any }) -> Box,
 	readData: (self: CStructInfo, target: (Ref|Box), offset: number?) -> { any },
 	writeData: (self: CStructInfo, target: (Ref|Box), table: { any }, offset: number?) -> (),
+	copyData: (self: CStructInfo, dst: (Ref|Box), src: (Ref|Box), dst_offset: number?, src_offset: number?) -> (),
+
+	offset: (self: CStructInfo, index: number) -> number,
+	field: (self: CStructInfo, index: number) -> CTypes,
 }
 
 export type CVoidInfo = {
@@ -163,21 +169,6 @@ export type Closure = {
 
 local c = {}
 
-c.u8 = {} :: u8
-c.u16 = {} :: u16
-c.u32 = {} :: u32
-c.u64 = {} :: u64
-c.u128 = {} :: u128
-c.i8 = {} :: i8
-c.i16 = {} :: i16
-c.i32 = {} :: i32
-c.i64 = {} :: i64
-c.i128 = {} :: i128
-c.f32 = {} :: f32
-c.f64 = {} :: f64
-c.usize = {} :: usize
-c.isize = {} :: isize
-
 c.char = {} :: char
 c.float = {} :: float
 c.double = {} :: double
@@ -206,6 +197,21 @@ local ffi = {}
 
 ffi.c = c
 
+ffi.u8 = {} :: u8
+ffi.u16 = {} :: u16
+ffi.u32 = {} :: u32
+ffi.u64 = {} :: u64
+ffi.u128 = {} :: u128
+ffi.i8 = {} :: i8
+ffi.i16 = {} :: i16
+ffi.i32 = {} :: i32
+ffi.i64 = {} :: i64
+ffi.i128 = {} :: i128
+ffi.f32 = {} :: f32
+ffi.f64 = {} :: f64
+ffi.usize = {} :: usize
+ffi.isize = {} :: isize
+
 function ffi.nullRef(): Ref
 	return nil :: any
 end