diff --git a/.gitattributes b/.gitattributes
index 3cb3e3e..38d1ab4 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -6,3 +6,7 @@
 
 # Ensure all txt files within tests use LF
 tests/**/*.txt eol=lf
+
+# Remove test c and typescript from language list
+tests/ffi/**/*.c linguist-vendored
+tests/ffi/**/*.ts linguist-vendored
diff --git a/.gitignore b/.gitignore
index 6f7b83e..b7719c3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,15 @@ luneDocs.json
 luneTypes.d.luau
 
 # Files generated by runtime or build scripts
+
 scripts/brick_color.rs
 scripts/font_enum_map.rs
 scripts/physical_properties_enum_map.rs
+
+# Files generated by tests
+
+/tests/ffi/**/*.so
+
+# Core dump file
+
+/core
diff --git a/Cargo.lock b/Cargo.lock
index 5bc12be..4ec7933 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -787,6 +787,29 @@ dependencies = [
  "syn 2.0.79",
 ]
 
+[[package]]
+name = "dlopen2"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1297103d2bbaea85724fcee6294c2d50b1081f9ad47d0f6f6f61eda65315a6"
+dependencies = [
+ "dlopen2_derive",
+ "libc",
+ "once_cell",
+ "winapi",
+]
+
+[[package]]
+name = "dlopen2_derive"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.79",
+]
+
 [[package]]
 name = "dunce"
 version = "1.0.5"
@@ -1422,9 +1445,28 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
 
 [[package]]
 name = "libc"
-version = "0.2.160"
+version = "0.2.162"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0b21006cd1874ae9e650973c565615676dc4a274c965bb0a73796dac838ce4f"
+checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
+
+[[package]]
+name = "libffi"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce826c243048e3d5cec441799724de52e2d42f820468431fc3fceee2341871e2"
+dependencies = [
+ "libc",
+ "libffi-sys",
+]
+
+[[package]]
+name = "libffi-sys"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f36115160c57e8529781b4183c2bb51fdc1f6d6d1ed345591d84be7703befb3c"
+dependencies = [
+ "cc",
+]
 
 [[package]]
 name = "libloading"
@@ -1557,6 +1599,7 @@ name = "lune-std"
 version = "0.1.5"
 dependencies = [
  "lune-std-datetime",
+ "lune-std-ffi",
  "lune-std-fs",
  "lune-std-luau",
  "lune-std-net",
@@ -1585,6 +1628,19 @@ dependencies = [
  "thiserror",
 ]
 
+[[package]]
+name = "lune-std-ffi"
+version = "0.1.1"
+dependencies = [
+ "dlopen2",
+ "libc",
+ "libffi",
+ "lune-utils",
+ "mlua",
+ "mlua-sys",
+ "num",
+]
+
 [[package]]
 name = "lune-std-fs"
 version = "0.1.2"
@@ -1880,6 +1936,39 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "num"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
+dependencies = [
+ "num-bigint",
+ "num-complex",
+ "num-integer",
+ "num-iter",
+ "num-rational",
+ "num-traits",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-complex"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
+dependencies = [
+ "num-traits",
+]
+
 [[package]]
 name = "num-conv"
 version = "0.1.0"
@@ -1895,6 +1984,28 @@ dependencies = [
  "num-traits",
 ]
 
+[[package]]
+name = "num-iter"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
+dependencies = [
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+]
+
 [[package]]
 name = "num-traits"
 version = "0.2.19"
diff --git a/Cargo.toml b/Cargo.toml
index 221a0fb..f32f08f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,6 +15,7 @@ members = [
     "crates/lune-std-serde",
     "crates/lune-std-stdio",
     "crates/lune-std-task",
+    "crates/lune-std-ffi",
     "crates/lune-utils",
     "crates/mlua-luau-scheduler",
 ]
diff --git a/crates/lune-std-ffi/Cargo.toml b/crates/lune-std-ffi/Cargo.toml
new file mode 100644
index 0000000..78eebda
--- /dev/null
+++ b/crates/lune-std-ffi/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "lune-std-ffi"
+version = "0.1.1"
+edition = "2021"
+license = "MPL-2.0"
+repository = "https://github.com/lune-org/lune"
+description = "Lune standard library - FFI"
+
+[lib]
+path = "src/lib.rs"
+
+[lints]
+workspace = true
+
+[dependencies]
+mlua = { version = "0.9.9", features = ["luau"] }
+mlua-sys = { version = "0.6.2", features = ["luau"] }
+num = "0.4.3"
+dlopen2 = "0.7.0"
+libc = "0.2.162"
+
+libffi = "3.2.0"
+
+lune-utils = { version = "0.1.3", path = "../lune-utils" }
diff --git a/crates/lune-std-ffi/README.md b/crates/lune-std-ffi/README.md
new file mode 100644
index 0000000..a6ad488
--- /dev/null
+++ b/crates/lune-std-ffi/README.md
@@ -0,0 +1,146 @@
+<!-- markdownlint-disable MD033 -->
+
+# `lune-std-ffi`
+
+## Tests & Benchmarks
+
+See [tests/ffi](../../tests/ffi/README.md)
+
+## TODO
+
+- [CString](./src/c/string_info.rs)
+- Add buffer as owned data support
+- Add math operation for numeric types
+  > Provide related methods: `CTypeInfo:add(target, from1, from2, ...)` and `:sub` `:mul` `:div` `:mod` `:pow` `:max` `:min` `:gt` `:lt`  
+  > Luau cannot handle f64, i64 or i128, so we should provide math operation for it
+- Add bit operation for box/ref
+  > Luau only supports 32bit bit operations
+- Add wchar and wstring support
+  > For windows API support
+- Add varargs support
+- Array argument in cfn
+- [More box/ref methods](./src/data/helper.rs)
+  - writeString
+  - readString
+  - writeBase64
+  - readBase64
+
+## Code structure
+
+### /c
+
+Define C-ABI type information and provide conversion and casting
+
+**Structs:** C ABI type informations
+
+- [**Struct `CArrInfo`:**](./src/c/arr_info.rs) Represents C Array type
+- [**Struct `CPtrInfo`:**](./src/c/ptr_info.rs) Represents C Pointer type
+- [**Struct `CFnInfo`:**](./src/c/fn_info.rs) Represents C Function signature
+  > provide `CallableData` and `ClosureData` creator
+- [**Struct `CStructInfo`:**](./src/c/struct_info.rs) Represents C Struct type
+- [**Struct `CTypeInfo<T>`:**](./src/c/type_info.rs) Represents C type, extended in `/c/types`
+
+<details><summary><a href="./src/c/helper.rs"><strong>Mod <code>helper.rs</code>:</strong></a> C ABI type helper</summary>
+
+- **Function `get_conv`, `get_conv_list`:**
+  get `FfiConvert` from userdata (CStruct, CArr, CPtr, CTypes)
+- **Function `get_middle_type`, `get_middle_type_list`:**
+  get **`libffi::middle::Type`:** from userdata (CFn, CStruct, CArr, CPtr, CTypes)
+- **Function `get_size`:**
+  get size from userdata
+- **Function `has_void`:**
+  check table has void type
+- **Function `stringify`:**
+  stringify any type userdata
+- **Function `get_name`:**
+  get type name from ctype userdata, used for pretty-print
+- **Function `is_ctype`:** check userdata is ctype
+- **Mod `method_provider`:** provide common userdata method implements
+
+</details>
+
+#### /c/types
+
+Export fixed-size source time known types and non-fixed compile time known types
+mod.rs implememts type-casting for all CTypes
+
+<details><summary><a href="./src/c/types/mod.rs"><strong>Mod <code>ctype_helper</code>:</strong></a> CTypeInfo helper</summary>
+
+- **Function `get_conv`:**
+  get `FfiConvert` from ctype userdata, used for struct and array conversion
+- **Function `get_middle_type`:**
+  get **`libffi::middle::Type`:** from ctype userdata
+- **Function `get_size`:**
+  get size from ctype userdata
+- **Function `get_name`:**
+  get type name from ctype userdata, used for pretty-print
+- **Function `is_ctype`:** check userdata is ctype
+
+</details>
+
+---
+
+### /data
+
+**Structs:** Provide memory userdata
+
+- [**Struct `BoxData`:**](./src/data/box_data/mod.rs) A heap allocated memory with user definable lifetime
+- [**Struct `LibData`:**](./src/data/lib_data.rs) A dynamic opened library
+- [**Struct `RefData`:**](./src/data/ref_data/mod.rs) A reference that can be used for receiving return data from external function or pass pointer arguments
+
+**Structs:** Provide function(pointer) userdata
+
+- [**Struct `CallableData`:**](./src/data/callable_data.rs) A callable function, which can be created from function pointer
+- [**Struct `ClosureData`:**](./src/data/closure_data.rs) A closure pointer, which can be created from lua function and can be used for callback
+
+---
+
+### /ffi
+
+**Traits:** Provide ABI shared common type information trait
+
+- **Trait `FfiSize`**
+- **Trait `FfiSignedness`**
+
+<ul><li><details><summary><strong>Trait <code>FfiConvert</code>:</strong> Provide methods for read LuaValue from FfiData or write LuaValue into FfiData</summary>
+
+- **Method `value_into_data`:** set data with lua value
+- **Method `value_from_data`:** get lua value from data
+- **Method `copy_data`:** copy sized data into another data
+- **Method `stringify_data`:** stringify data with specific type
+
+</details></li></ul>
+
+**Structs:** Provide call information
+
+- **Struct `FfiArg`:** Used for argument boundary checking and callback argument ref flag
+- **Struct `FfiResult`:** Used for result boundary checking
+
+<details><summary><strong>Trait <code>FfiData</code>:</strong> Provide common data handle, including methods below</summary>
+
+- **Method `check_inner_boundary`:** check boundary with offset and size
+- **Method `get_inner_pointer`:** returns raw pointer `*mut ()`
+- **Method `is_writable`**
+- **Method `is_readable`**
+- **Method `copy_from`** copy data from another data
+
+</details>
+
+> Note: `GetFfiData` trait in `data/mod.rs` provides `(LuaValue | LuaAnyUserData).get_data_handle() -> FfiData` method
+
+**Mods:** Provide common helper functions
+
+- [**Mod `association.rs`:**](./src/ffi/association.rs) GC utility, used for inner, ret and arg type holding in subtype
+- [**Mod `bit_mask.rs`:**](./src/ffi/bit_mask.rs) u8 bitfield helper
+- [**Mod `cast.rs`:**](./src/ffi/cast.rs) num cast library wrapper
+  - **Function `num_cast<From, Into>(from: FfiData, from: FfiData)`:**
+    Cast number type value inno another number type
+
+<ul><li><details><summary><a href="./src/c/struct_info.rs"><strong>Mod <code>libffi_helper.rs</code>:</strong></a> libffi library helper</summary>
+
+- **Const `FFI_STATUS_NAMES`:** Stringify `ffi_status`
+- **Function `get_ensured_size`:** Returns ensured size of `ffi_type`
+- **Const `SIZE_OF_POINTER`:** Platform specific pointer size (Compile time known)
+- **Function `ffi_status_assert`:** Convert `ffi_status` to `LuaResult<()>`
+
+</details></li></ul>
diff --git a/crates/lune-std-ffi/src/c/arr_info.rs b/crates/lune-std-ffi/src/c/arr_info.rs
new file mode 100644
index 0000000..1668bd6
--- /dev/null
+++ b/crates/lune-std-ffi/src/c/arr_info.rs
@@ -0,0 +1,180 @@
+use std::cell::Ref;
+
+use libffi::middle::Type;
+use mlua::prelude::*;
+
+use super::{association_names::CARR_INNER, helper, method_provider};
+use crate::ffi::{association, libffi_helper::get_ensured_size, FfiConvert, FfiData, FfiSize};
+
+// Series of some type.
+// It provides the final size and the offset of the index,
+// but does not allow multidimensional arrays because of API complexity.
+// Multidimensional arrays can be implemented
+// because they are a series of transcribed one-dimensional arrays. (flatten)
+// We can simply provide array type with struct.
+// See: https://stackoverflow.com/a/43525176
+pub struct CArrInfo {
+    struct_type: Type,
+    length: usize,
+    size: usize,
+    inner_size: usize,
+    inner_conv: *const dyn FfiConvert,
+}
+
+impl CArrInfo {
+    pub fn new(
+        element_type: Type,
+        length: usize,
+        inner_conv: *const dyn FfiConvert,
+    ) -> LuaResult<Self> {
+        let inner_size = get_ensured_size(element_type.as_raw_ptr())?;
+        let struct_type = Type::structure(vec![element_type.clone(); length]);
+
+        Ok(Self {
+            struct_type,
+            length,
+            size: inner_size * length,
+            inner_size,
+            inner_conv,
+        })
+    }
+
+    pub fn from_userdata<'lua>(
+        lua: &'lua Lua,
+        type_userdata: &LuaAnyUserData<'lua>,
+        length: usize,
+    ) -> LuaResult<LuaAnyUserData<'lua>> {
+        let fields = helper::get_middle_type(type_userdata)?;
+        let conv = unsafe { helper::get_conv(type_userdata)? };
+        let carr = lua.create_userdata(Self::new(fields, length, conv)?)?;
+
+        association::set(lua, CARR_INNER, &carr, type_userdata)?;
+        Ok(carr)
+    }
+
+    pub fn get_length(&self) -> usize {
+        self.length
+    }
+
+    pub fn get_middle_type(&self) -> Type {
+        self.struct_type.clone()
+    }
+
+    // Stringify for pretty-print
+    // ex: <CArr( u8, length = 8 )>
+    pub fn stringify(lua: &Lua, userdata: &LuaAnyUserData) -> LuaResult<String> {
+        let this = userdata.borrow::<CArrInfo>()?;
+        if let Some(LuaValue::UserData(inner_userdata)) =
+            association::get(lua, CARR_INNER, userdata)?
+        {
+            Ok(format!(
+                " {}, length = {} ",
+                helper::pretty_format(lua, &inner_userdata)?,
+                this.length,
+            ))
+        } else {
+            Err(LuaError::external("Failed to retrieve inner type"))
+        }
+    }
+}
+
+impl FfiSize for CArrInfo {
+    fn get_size(&self) -> usize {
+        self.size
+    }
+}
+
+impl FfiConvert for CArrInfo {
+    unsafe fn value_into_data<'lua>(
+        &self,
+        lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+        value: LuaValue<'lua>,
+    ) -> LuaResult<()> {
+        let LuaValue::Table(ref table) = value else {
+            return Err(LuaError::external("Value is not a table"));
+        };
+        for i in 0..self.length {
+            let field_offset = (i * self.inner_size) as isize;
+            let data: LuaValue = table.get(i + 1)?;
+
+            self.inner_conv.as_ref().unwrap().value_into_data(
+                lua,
+                field_offset + offset,
+                data_handle,
+                data,
+            )?;
+        }
+        Ok(())
+    }
+
+    unsafe fn value_from_data<'lua>(
+        &self,
+        lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<LuaValue<'lua>> {
+        let table = lua.create_table_with_capacity(self.length, 0)?;
+        for i in 0..self.length {
+            let field_offset = (i * self.inner_size) as isize;
+            table.set(
+                i + 1,
+                self.inner_conv.as_ref().unwrap().value_from_data(
+                    lua,
+                    field_offset + offset,
+                    data_handle,
+                )?,
+            )?;
+        }
+        Ok(LuaValue::Table(table))
+    }
+
+    unsafe fn copy_data(
+        &self,
+        _lua: &Lua,
+        dst_offset: isize,
+        src_offset: isize,
+        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(),
+        );
+        Ok(())
+    }
+}
+
+impl LuaUserData for CArrInfo {
+    fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
+        fields.add_field_method_get("size", |_lua, this| Ok(this.get_size()));
+        fields.add_field_method_get("length", |_lua, this| Ok(this.get_length()));
+        fields.add_field_function_get("inner", |lua, this: LuaAnyUserData| {
+            association::get(lua, CARR_INNER, this)?
+                .ok_or_else(|| LuaError::external("Failed to retrieve inner field"))
+        });
+    }
+
+    fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
+        // Subtype
+        method_provider::provide_ptr(methods);
+
+        // ToString
+        method_provider::provide_to_string(methods);
+
+        // Realize
+        method_provider::provide_box(methods);
+        method_provider::provide_read_data(methods);
+        method_provider::provide_write_data(methods);
+        method_provider::provide_copy_data(methods);
+
+        methods.add_method("offset", |_lua, this, offset: isize| {
+            if this.length > (offset as usize) && offset >= 0 {
+                Ok(this.inner_size * (offset as usize))
+            } else {
+                Err(LuaError::external("Out of index"))
+            }
+        });
+    }
+}
diff --git a/crates/lune-std-ffi/src/c/fn_info.rs b/crates/lune-std-ffi/src/c/fn_info.rs
new file mode 100644
index 0000000..963d8a0
--- /dev/null
+++ b/crates/lune-std-ffi/src/c/fn_info.rs
@@ -0,0 +1,218 @@
+use libffi::middle::{Cif, Type};
+use mlua::prelude::*;
+
+use super::{
+    association_names::{
+        CALLABLE_CFN, CALLABLE_REF, CFN_ARGS, CFN_RESULT, CLOSURE_CFN, CLOSURE_FUNC,
+    },
+    ctype_helper::is_ctype,
+    helper, method_provider, CArrInfo, CPtrInfo, CStructInfo,
+};
+use crate::{
+    data::{CallableData, ClosureData, RefData, RefFlag},
+    ffi::{
+        association, bit_field::*, libffi_helper::SIZE_OF_POINTER, FfiArg, FfiData, FfiResult,
+        FfiSignedness, FfiSize,
+    },
+};
+
+// Function pointer type
+pub struct CFnInfo {
+    cif: Cif,
+    arg_info_list: Vec<FfiArg>,
+    result_info: FfiResult,
+}
+
+impl FfiSignedness for CFnInfo {
+    fn get_signedness(&self) -> bool {
+        false
+    }
+}
+
+impl FfiSize for CFnInfo {
+    fn get_size(&self) -> usize {
+        SIZE_OF_POINTER
+    }
+}
+
+const CALLBACK_ARG_REF_FLAG_TYPE: u8 = RefFlag::Readable.value();
+const CALLBACK_ARG_REF_FLAG_PTR: u8 = RefFlag::Dereferenceable.value() | RefFlag::Readable.value();
+const CALLBACK_ARG_REF_FLAG_ARR: u8 = RefFlag::Readable.value() | RefFlag::Offsetable.value();
+const CALLBACK_ARG_REF_FLAG_STRUCT: u8 = RefFlag::Readable.value() | RefFlag::Offsetable.value();
+const CALLBACK_ARG_REF_FLAG_CFN: u8 = RefFlag::Function.value();
+
+fn create_arg_info(userdata: &LuaAnyUserData) -> LuaResult<FfiArg> {
+    let callback_ref_flag = if is_ctype(userdata) {
+        CALLBACK_ARG_REF_FLAG_TYPE
+    } else if userdata.is::<CPtrInfo>() {
+        CALLBACK_ARG_REF_FLAG_PTR
+    } else if userdata.is::<CArrInfo>() {
+        CALLBACK_ARG_REF_FLAG_ARR
+    } else if userdata.is::<CStructInfo>() {
+        CALLBACK_ARG_REF_FLAG_STRUCT
+    } else if userdata.is::<CFnInfo>() {
+        CALLBACK_ARG_REF_FLAG_CFN
+    } else {
+        return Err(LuaError::external("Unexpected argument type"));
+    };
+    Ok(FfiArg {
+        size: helper::get_size(userdata)?,
+        callback_ref_flag,
+    })
+}
+
+impl CFnInfo {
+    pub fn new(
+        args: Vec<Type>,
+        ret: Type,
+        arg_info_list: Vec<FfiArg>,
+        result_info: FfiResult,
+    ) -> LuaResult<Self> {
+        Ok(Self {
+            cif: Cif::new(args.clone(), ret.clone()),
+            arg_info_list,
+            result_info,
+        })
+    }
+
+    pub fn from_table<'lua>(
+        lua: &'lua Lua,
+        arg_table: LuaTable,
+        ret: LuaAnyUserData,
+    ) -> LuaResult<LuaAnyUserData<'lua>> {
+        if helper::has_void(&arg_table)? {
+            return Err(LuaError::external("Arguments can not include void type"));
+        }
+
+        let args_types = helper::get_middle_type_list(&arg_table)?;
+        let ret_type = helper::get_middle_type(&ret)?;
+
+        let arg_info_list = helper::create_list(&arg_table, create_arg_info)?;
+        let result_info = FfiResult {
+            size: helper::get_size(&ret)?,
+        };
+
+        let cfn =
+            lua.create_userdata(Self::new(args_types, ret_type, arg_info_list, result_info)?)?;
+
+        // Create association to hold argument and result type
+        association::set(lua, CFN_ARGS, &cfn, arg_table)?;
+        association::set(lua, CFN_RESULT, &cfn, ret)?;
+
+        Ok(cfn)
+    }
+
+    // Stringify for pretty-print
+    // ex: <CFn( (u8, i32) -> u8 )>
+    pub fn stringify(lua: &Lua, userdata: &LuaAnyUserData) -> LuaResult<String> {
+        let mut result = String::from(" (");
+        if let (Some(LuaValue::Table(arg_table)), Some(LuaValue::UserData(result_userdata))) = (
+            association::get(lua, CFN_ARGS, userdata)?,
+            association::get(lua, CFN_RESULT, userdata)?,
+        ) {
+            let len = arg_table.raw_len();
+            for arg_index in 1..=len {
+                let arg_userdata: LuaAnyUserData = arg_table.raw_get(arg_index)?;
+                let pretty_formatted = helper::pretty_format(lua, &arg_userdata)?;
+                result.push_str(
+                    (if len == arg_index {
+                        pretty_formatted
+                    } else {
+                        format!("{pretty_formatted}, ")
+                    })
+                    .as_str(),
+                );
+            }
+            result.push_str(
+                format!(") -> {} ", helper::pretty_format(lua, &result_userdata)?,).as_str(),
+            );
+            Ok(result)
+        } else {
+            Err(LuaError::external("Failed to retrieve inner type"))
+        }
+    }
+
+    // Create ClosureData with lua function
+    pub fn create_closure<'lua>(
+        &self,
+        lua: &'lua Lua,
+        this: &LuaAnyUserData,
+        lua_function: LuaFunction<'lua>,
+    ) -> LuaResult<LuaAnyUserData<'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)?,
+        )?;
+
+        association::set(lua, CLOSURE_CFN, &closure_data, this)?;
+        association::set(lua, CLOSURE_FUNC, &closure_data, lua_function)?;
+
+        Ok(closure_data)
+    }
+
+    // Create CallableData from RefData
+    pub fn create_callable<'lua>(
+        &self,
+        lua: &'lua Lua,
+        this: &LuaAnyUserData,
+        target_ref: &LuaAnyUserData,
+    ) -> LuaResult<LuaAnyUserData<'lua>> {
+        if !target_ref.is::<RefData>() {
+            return Err(LuaError::external("Argument 'functionRef' must be RefData"));
+        }
+
+        let ffi_ref = target_ref.borrow::<RefData>()?;
+        if u8_test_not(ffi_ref.flags, RefFlag::Function.value()) {
+            return Err(LuaError::external(
+                "Argument 'functionRef' is not a valid function reference",
+            ));
+        }
+
+        let callable = lua.create_userdata(unsafe {
+            CallableData::new(
+                self.cif.as_raw_ptr(),
+                self.arg_info_list.clone(),
+                self.result_info.clone(),
+                ffi_ref.get_inner_pointer(),
+            )
+        })?;
+
+        association::set(lua, CALLABLE_CFN, &callable, this)?;
+        association::set(lua, CALLABLE_REF, &callable, target_ref)?;
+
+        Ok(callable)
+    }
+
+    pub fn get_middle_type() -> Type {
+        Type::pointer()
+    }
+}
+
+impl LuaUserData for CFnInfo {
+    fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
+        fields.add_field_method_get("size", |_lua, _this| Ok(SIZE_OF_POINTER));
+    }
+    fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
+        // ToString
+        method_provider::provide_to_string(methods);
+
+        // Realize
+        methods.add_function(
+            "closure",
+            |lua, (cfn, func): (LuaAnyUserData, LuaFunction)| {
+                let this = cfn.borrow::<CFnInfo>()?;
+                this.create_closure(lua, cfn.as_ref(), func)
+            },
+        );
+        methods.add_function(
+            "callable",
+            |lua, (cfn, target): (LuaAnyUserData, LuaAnyUserData)| {
+                let this = cfn.borrow::<CFnInfo>()?;
+                this.create_callable(lua, cfn.as_ref(), &target)
+            },
+        );
+    }
+}
diff --git a/crates/lune-std-ffi/src/c/helper.rs b/crates/lune-std-ffi/src/c/helper.rs
new file mode 100644
index 0000000..b56ed7f
--- /dev/null
+++ b/crates/lune-std-ffi/src/c/helper.rs
@@ -0,0 +1,318 @@
+use libffi::middle::Type;
+use lune_utils::fmt::{pretty_format_value, ValueFormatConfig};
+use mlua::prelude::*;
+
+use super::{ctype_helper, void_info::CVoidInfo, CArrInfo, CFnInfo, CPtrInfo, CStructInfo};
+use crate::{
+    data::{BoxData, GetFfiData},
+    ffi::{FfiConvert, FfiSize},
+};
+
+pub mod method_provider {
+    use super::*;
+
+    // Implement tostring
+    pub fn provide_to_string<'lua, Target, M>(methods: &mut M)
+    where
+        Target: 'static,
+        M: LuaUserDataMethods<'lua, Target>,
+    {
+        methods.add_meta_function(LuaMetaMethod::ToString, |lua, this: LuaAnyUserData| {
+            stringify(lua, &this)
+        });
+    }
+
+    // Implement ptr method
+    pub fn provide_ptr<'lua, Target, M>(methods: &mut M)
+    where
+        Target: 'static,
+        M: LuaUserDataMethods<'lua, Target>,
+    {
+        methods.add_function("ptr", |lua, this: LuaAnyUserData| {
+            CPtrInfo::from_userdata(lua, &this)
+        });
+    }
+
+    // Implement arr method
+    pub fn provide_arr<'lua, Target, M>(methods: &mut M)
+    where
+        Target: 'static,
+        M: LuaUserDataMethods<'lua, Target>,
+    {
+        methods.add_function("arr", |lua, (this, length): (LuaAnyUserData, usize)| {
+            CArrInfo::from_userdata(lua, &this, length)
+        });
+    }
+
+    // Implement readData method
+    pub fn provide_read_data<'lua, Target, M>(methods: &mut M)
+    where
+        Target: FfiSize + FfiConvert + 'static,
+        M: LuaUserDataMethods<'lua, Target>,
+    {
+        methods.add_method(
+            "readData",
+            |lua, this, (target, offset): (LuaAnyUserData, Option<isize>)| {
+                let offset = offset.unwrap_or(0);
+
+                let data_handle = &target.get_ffi_data()?;
+                if !data_handle.check_inner_boundary(offset, this.get_size()) {
+                    return Err(LuaError::external("Out of bounds"));
+                }
+                if !data_handle.is_readable() {
+                    return Err(LuaError::external("Unreadable data"));
+                }
+
+                unsafe { this.value_from_data(lua, offset, data_handle) }
+            },
+        );
+    }
+
+    // Implement writeData method
+    pub fn provide_write_data<'lua, Target, M>(methods: &mut M)
+    where
+        Target: FfiSize + FfiConvert + 'static,
+        M: LuaUserDataMethods<'lua, Target>,
+    {
+        methods.add_method(
+            "writeData",
+            |lua, this, (target, value, offset): (LuaAnyUserData, LuaValue, Option<isize>)| {
+                let offset = offset.unwrap_or(0);
+
+                let data_handle = &target.get_ffi_data()?;
+                // use or functions
+                if !data_handle.check_inner_boundary(offset, this.get_size()) {
+                    return Err(LuaError::external("Out of bounds"));
+                }
+                if !data_handle.is_writable() {
+                    return Err(LuaError::external("Unwritable data"));
+                }
+
+                unsafe { this.value_into_data(lua, offset, data_handle, value) }
+            },
+        );
+    }
+
+    // Implement copyData method
+    pub fn provide_copy_data<'lua, Target, M>(methods: &mut M)
+    where
+        Target: FfiSize + FfiConvert + 'static,
+        M: LuaUserDataMethods<'lua, Target>,
+    {
+        methods.add_method(
+            "copyData",
+            |lua,
+             this,
+             (dst, src, dst_offset, src_offset): (
+                LuaAnyUserData,
+                LuaAnyUserData,
+                Option<isize>,
+                Option<isize>,
+            )| {
+                let dst_offset = dst_offset.unwrap_or(0);
+                let src_offset = src_offset.unwrap_or(0);
+
+                let dst = &dst.get_ffi_data()?;
+                // use or functions
+                if !dst.check_inner_boundary(dst_offset, this.get_size()) {
+                    return Err(LuaError::external("Destination out of bounds"));
+                }
+                if !dst.is_writable() {
+                    return Err(LuaError::external("Destination is unwritable"));
+                }
+
+                let src = &src.get_ffi_data()?;
+                if !src.check_inner_boundary(dst_offset, this.get_size()) {
+                    return Err(LuaError::external("Source out of bounds"));
+                }
+                if !src.is_readable() {
+                    return Err(LuaError::external("Source is unreadable"));
+                }
+
+                unsafe { this.copy_data(lua, dst_offset, src_offset, dst, src) }
+            },
+        );
+    }
+
+    // Implement stringifyData method
+    pub fn provide_stringify_data<'lua, Target, M>(methods: &mut M)
+    where
+        Target: FfiSize + FfiConvert + 'static,
+        M: LuaUserDataMethods<'lua, Target>,
+    {
+        methods.add_method(
+            "stringifyData",
+            |lua, this, (target, offset): (LuaAnyUserData, Option<isize>)| unsafe {
+                this.stringify_data(lua, offset.unwrap_or(0), &target.get_ffi_data()?)
+            },
+        );
+    }
+
+    // Implement box method
+    pub fn provide_box<'lua, Target, M>(methods: &mut M)
+    where
+        Target: FfiSize + FfiConvert + 'static,
+        M: LuaUserDataMethods<'lua, Target>,
+    {
+        methods.add_method("box", |lua, this, value: LuaValue| {
+            let result = lua.create_userdata(BoxData::new(this.get_size()))?;
+            unsafe { this.value_into_data(lua, 0, &result.get_ffi_data()?, value)? };
+            Ok(result)
+        });
+    }
+}
+
+fn get_userdata(value: LuaValue) -> LuaResult<LuaAnyUserData> {
+    if let LuaValue::UserData(field_type) = value {
+        Ok(field_type)
+    } else {
+        Err(LuaError::external(format!(
+            "CStruct, CType, CFn, CVoid or CArr is required but got {}",
+            pretty_format_value(&value, &ValueFormatConfig::new())
+        )))
+    }
+}
+
+// Create vec<T> from table with (userdata)->T
+pub fn create_list<T>(
+    table: &LuaTable,
+    callback: fn(&LuaAnyUserData) -> LuaResult<T>,
+) -> LuaResult<Vec<T>> {
+    let len: usize = table.raw_len();
+    let mut list = Vec::<T>::with_capacity(len);
+
+    for i in 0..len {
+        let value: LuaValue = table.raw_get(i + 1)?;
+        list.push(callback(&get_userdata(value)?)?);
+    }
+
+    Ok(list)
+}
+
+// Get the dynamic FfiConvert handle from the userData
+// This is intended to avoid lookup userdata and lua table every time. (eg: struct)
+// The userdata must live longer than the FfiConvert handle
+pub unsafe fn get_conv(userdata: &LuaAnyUserData) -> LuaResult<*const dyn FfiConvert> {
+    if userdata.is::<CStructInfo>() {
+        Ok(userdata.to_pointer().cast::<CStructInfo>() as *const dyn FfiConvert)
+    } else if userdata.is::<CArrInfo>() {
+        Ok(userdata.to_pointer().cast::<CArrInfo>() as *const dyn FfiConvert)
+    } else if userdata.is::<CPtrInfo>() {
+        Ok(userdata.to_pointer().cast::<CPtrInfo>() as *const dyn FfiConvert)
+    } else {
+        ctype_helper::get_conv(userdata)
+    }
+}
+pub unsafe fn get_conv_list(table: &LuaTable) -> LuaResult<Vec<*const dyn FfiConvert>> {
+    create_list(table, |userdata| get_conv(userdata))
+}
+
+// Get libffi_type from ctype userdata
+pub fn get_middle_type(userdata: &LuaAnyUserData) -> LuaResult<Type> {
+    if userdata.is::<CStructInfo>() {
+        Ok(userdata.borrow::<CStructInfo>()?.get_middle_type())
+    } else if let Some(middle_type) = ctype_helper::get_middle_type(userdata)? {
+        Ok(middle_type)
+    } else if userdata.is::<CArrInfo>() {
+        Ok(userdata.borrow::<CArrInfo>()?.get_middle_type())
+    } else if userdata.is::<CPtrInfo>() {
+        Ok(CPtrInfo::get_middle_type())
+    } else if userdata.is::<CVoidInfo>() {
+        Ok(CVoidInfo::get_middle_type())
+    } else if userdata.is::<CFnInfo>() {
+        Ok(CFnInfo::get_middle_type())
+    } else {
+        Err(LuaError::external(format!(
+            "CStruct, CType, CFn, CVoid or CArr is required but got {}",
+            pretty_format_value(
+                // Since the data is in the Lua location,
+                // there is no problem with the clone.
+                &LuaValue::UserData(userdata.to_owned()),
+                &ValueFormatConfig::new()
+            )
+        )))
+    }
+}
+pub fn get_middle_type_list(table: &LuaTable) -> LuaResult<Vec<Type>> {
+    create_list(table, get_middle_type)
+}
+
+// Get type size from ctype userdata
+pub fn get_size(userdata: &LuaAnyUserData) -> LuaResult<usize> {
+    if userdata.is::<CStructInfo>() {
+        Ok(userdata.borrow::<CStructInfo>()?.get_size())
+    } else if userdata.is::<CArrInfo>() {
+        Ok(userdata.borrow::<CArrInfo>()?.get_size())
+    } else if userdata.is::<CPtrInfo>() {
+        Ok(userdata.borrow::<CPtrInfo>()?.get_size())
+    } else if userdata.is::<CVoidInfo>() {
+        Ok(userdata.borrow::<CVoidInfo>()?.get_size())
+    } else if userdata.is::<CFnInfo>() {
+        Ok(userdata.borrow::<CFnInfo>()?.get_size())
+    } else {
+        ctype_helper::get_size(userdata)
+    }
+}
+
+// Check lua table has void ctype userdata
+pub fn has_void(table: &LuaTable) -> LuaResult<bool> {
+    for i in 0..table.raw_len() {
+        let value: LuaValue = table.raw_get(i + 1)?;
+        if get_userdata(value)?.is::<CVoidInfo>() {
+            return Ok(false);
+        }
+    }
+    Ok(false)
+}
+
+// Stringify any c-type userdata recursively
+pub fn stringify(lua: &Lua, userdata: &LuaAnyUserData) -> LuaResult<String> {
+    if userdata.is::<CStructInfo>() {
+        CStructInfo::stringify(lua, userdata)
+    } else if userdata.is::<CArrInfo>() {
+        CArrInfo::stringify(lua, userdata)
+    } else if userdata.is::<CPtrInfo>() {
+        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 {
+        Ok(String::from("unknown"))
+    }
+}
+
+// Get name tag from ctype userdata
+pub fn get_tag_name(userdata: &LuaAnyUserData) -> LuaResult<String> {
+    Ok(if userdata.is::<CStructInfo>() {
+        String::from("CStructInfo")
+    } else if userdata.is::<CArrInfo>() {
+        String::from("CArrInfo")
+    } else if userdata.is::<CPtrInfo>() {
+        String::from("CPtrInfo")
+    } else if userdata.is::<CFnInfo>() {
+        String::from("CFnInfo")
+    } else if userdata.is::<CVoidInfo>() {
+        String::from("CVoidInfo")
+    } else if ctype_helper::is_ctype(userdata) {
+        String::from("CTypeInfo")
+    } else {
+        String::from("Unknown")
+    })
+}
+
+// Emulate 'print' for ctype userdata, but simplified
+// Used for print struct field, cfn arguments, etc...
+pub fn pretty_format(lua: &Lua, userdata: &LuaAnyUserData) -> LuaResult<String> {
+    if ctype_helper::is_ctype(userdata) {
+        stringify(lua, userdata)
+    } else {
+        Ok(format!(
+            "<{}({})>",
+            get_tag_name(userdata)?,
+            stringify(lua, userdata)?
+        ))
+    }
+}
diff --git a/crates/lune-std-ffi/src/c/mod.rs b/crates/lune-std-ffi/src/c/mod.rs
new file mode 100644
index 0000000..5bd9c1d
--- /dev/null
+++ b/crates/lune-std-ffi/src/c/mod.rs
@@ -0,0 +1,52 @@
+use lune_utils::TableBuilder;
+use mlua::prelude::*;
+
+mod arr_info;
+mod fn_info;
+pub mod helper;
+mod ptr_info;
+mod string_info;
+mod struct_info;
+mod type_info;
+mod types;
+mod void_info;
+
+pub use self::{
+    arr_info::CArrInfo,
+    fn_info::CFnInfo,
+    helper::method_provider,
+    ptr_info::CPtrInfo,
+    string_info::CStringInfo,
+    struct_info::CStructInfo,
+    type_info::{CTypeCast, CTypeInfo},
+    types::{ctype_helper, export_c_types, export_fixed_types},
+    void_info::CVoidInfo,
+};
+
+// Named registry keys
+mod association_names {
+    pub const CPTR_INNER: &str = "__cptr_inner";
+    pub const CARR_INNER: &str = "__carr_inner";
+    pub const CSTRUCT_INNER: &str = "__cstruct_inner";
+    pub const CFN_RESULT: &str = "__cfn_result";
+    pub const CFN_ARGS: &str = "__cfn_args";
+    pub const CALLABLE_REF: &str = "__callable_ref";
+    pub const CALLABLE_CFN: &str = "__callable_cfn";
+    pub const CLOSURE_FUNC: &str = "__closure_func";
+    pub const CLOSURE_CFN: &str = "__closure_cfn";
+}
+
+// Export c namespace
+pub fn export_c(lua: &Lua) -> LuaResult<LuaTable> {
+    TableBuilder::new(lua)?
+        .with_value("void", CVoidInfo::new())?
+        .with_values(export_c_types(lua)?)?
+        .with_function("struct", |lua, types: LuaTable| {
+            CStructInfo::from_table(lua, types)
+        })?
+        .with_function("fn", |lua, (args, ret): (LuaTable, LuaAnyUserData)| {
+            CFnInfo::from_table(lua, args, ret)
+        })?
+        .with_value("string", CStringInfo::new())?
+        .build_readonly()
+}
diff --git a/crates/lune-std-ffi/src/c/ptr_info.rs b/crates/lune-std-ffi/src/c/ptr_info.rs
new file mode 100644
index 0000000..aa774ec
--- /dev/null
+++ b/crates/lune-std-ffi/src/c/ptr_info.rs
@@ -0,0 +1,192 @@
+use std::cell::Ref;
+
+use libffi::middle::Type;
+use mlua::prelude::*;
+
+use super::{association_names::CPTR_INNER, ctype_helper, helper, method_provider};
+use crate::{
+    data::{GetFfiData, RefData, RefFlag, UNSIZED_BOUNDS},
+    ffi::{
+        association, libffi_helper::SIZE_OF_POINTER, FfiConvert, FfiData, FfiSignedness, FfiSize,
+    },
+};
+
+const READ_CPTR_REF_FLAGS: u8 = RefFlag::Dereferenceable.value() | RefFlag::Offsetable.value();
+const READ_REF_FLAGS: u8 =
+    RefFlag::Offsetable.value() | RefFlag::Readable.value() | RefFlag::Writable.value();
+
+pub struct CPtrInfo {
+    inner_is_cptr: bool,
+}
+
+impl FfiSignedness for CPtrInfo {
+    fn get_signedness(&self) -> bool {
+        false
+    }
+}
+
+impl FfiSize for CPtrInfo {
+    fn get_size(&self) -> usize {
+        SIZE_OF_POINTER
+    }
+}
+
+impl FfiConvert for CPtrInfo {
+    // Write address of RefData
+    unsafe fn value_into_data<'lua>(
+        &self,
+        _lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+        value: LuaValue<'lua>,
+    ) -> LuaResult<()> {
+        let LuaValue::UserData(value_userdata) = value else {
+            return Err(LuaError::external(format!(
+                "Value must be a RefData, BoxData or ClosureData, got {}",
+                value.type_name()
+            )));
+        };
+        *data_handle
+            .get_inner_pointer()
+            .byte_offset(offset)
+            .cast::<*mut ()>() = value_userdata.get_ffi_data()?.get_inner_pointer();
+        Ok(())
+    }
+
+    // Read address, create RefData
+    unsafe fn value_from_data<'lua>(
+        &self,
+        lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<LuaValue<'lua>> {
+        Ok(LuaValue::UserData(
+            lua.create_userdata(RefData::new(
+                *data_handle
+                    .get_inner_pointer()
+                    .byte_offset(offset)
+                    .cast::<*mut ()>(),
+                if self.inner_is_cptr {
+                    READ_CPTR_REF_FLAGS
+                } else {
+                    READ_REF_FLAGS
+                },
+                UNSIZED_BOUNDS,
+            ))?,
+        ))
+    }
+
+    // Copy Address
+    unsafe fn copy_data(
+        &self,
+        _lua: &Lua,
+        dst_offset: isize,
+        src_offset: isize,
+        dst: &Ref<dyn FfiData>,
+        src: &Ref<dyn FfiData>,
+    ) -> LuaResult<()> {
+        *dst.get_inner_pointer()
+            .byte_offset(dst_offset)
+            .cast::<*mut ()>() = *src
+            .get_inner_pointer()
+            .byte_offset(src_offset)
+            .cast::<*mut ()>();
+        Ok(())
+    }
+}
+
+impl CPtrInfo {
+    // Create pointer type with '.inner' field
+    // inner can be CArr, CType or CStruct
+    pub fn from_userdata<'lua>(
+        lua: &'lua Lua,
+        inner: &LuaAnyUserData,
+    ) -> LuaResult<LuaAnyUserData<'lua>> {
+        let value = lua.create_userdata(Self {
+            inner_is_cptr: inner.is::<CPtrInfo>(),
+        })?;
+
+        association::set(lua, CPTR_INNER, &value, inner)?;
+
+        Ok(value)
+    }
+
+    // Stringify CPtr with inner ctype
+    pub fn stringify(lua: &Lua, userdata: &LuaAnyUserData) -> LuaResult<String> {
+        if let LuaValue::UserData(inner_userdata) = userdata.get("inner")? {
+            let pretty_formatted = helper::pretty_format(lua, &inner_userdata)?;
+            Ok(if ctype_helper::is_ctype(&inner_userdata) {
+                pretty_formatted
+            } else {
+                format!(" {pretty_formatted} ")
+            })
+        } else {
+            Err(LuaError::external("Failed to retrieve inner type"))
+        }
+    }
+
+    // Return void*
+    pub fn get_middle_type() -> Type {
+        Type::pointer()
+    }
+}
+
+impl LuaUserData for CPtrInfo {
+    fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
+        fields.add_field_method_get("size", |_lua, _this| Ok(SIZE_OF_POINTER));
+        fields.add_field_function_get("inner", |lua, this| {
+            association::get(lua, CPTR_INNER, this)?
+                .ok_or_else(|| LuaError::external("Failed to retrieve inner type"))
+        });
+    }
+
+    fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
+        // Subtype
+        method_provider::provide_ptr(methods);
+        method_provider::provide_arr(methods);
+
+        // ToString
+        method_provider::provide_to_string(methods);
+
+        methods.add_method(
+            "readRef",
+            |lua,
+             this,
+             (target, offset, ref_data): (
+                LuaAnyUserData,
+                Option<isize>,
+                Option<LuaAnyUserData>,
+            )| unsafe {
+                if let Some(ref_userdata) = ref_data {
+                    if !ref_userdata.is::<RefData>() {
+                        return Err(LuaError::external(""));
+                    }
+                    RefData::update(
+                        lua,
+                        ref_userdata.clone(),
+                        *target
+                            .get_ffi_data()?
+                            .get_inner_pointer()
+                            .byte_offset(offset.unwrap_or(0))
+                            .cast::<*mut ()>(),
+                        if this.inner_is_cptr {
+                            READ_CPTR_REF_FLAGS
+                        } else {
+                            READ_REF_FLAGS
+                        },
+                        UNSIZED_BOUNDS,
+                    )?;
+                    Ok(LuaValue::UserData(ref_userdata))
+                } else {
+                    this.value_from_data(lua, offset.unwrap_or(0), &target.get_ffi_data()?)
+                }
+            },
+        );
+        methods.add_method(
+            "writeRef",
+            |lua, this, (target, value, offset): (LuaAnyUserData, LuaValue, Option<isize>)| unsafe {
+                this.value_into_data(lua, offset.unwrap_or(0), &target.get_ffi_data()?, value)
+            },
+        );
+    }
+}
diff --git a/crates/lune-std-ffi/src/c/string_info.rs b/crates/lune-std-ffi/src/c/string_info.rs
new file mode 100644
index 0000000..7fe3568
--- /dev/null
+++ b/crates/lune-std-ffi/src/c/string_info.rs
@@ -0,0 +1,13 @@
+// TODO:
+
+use mlua::prelude::*;
+
+pub struct CStringInfo();
+
+impl CStringInfo {
+    pub fn new() -> Self {
+        Self()
+    }
+}
+
+impl LuaUserData for CStringInfo {}
diff --git a/crates/lune-std-ffi/src/c/struct_info.rs b/crates/lune-std-ffi/src/c/struct_info.rs
new file mode 100644
index 0000000..b175a07
--- /dev/null
+++ b/crates/lune-std-ffi/src/c/struct_info.rs
@@ -0,0 +1,215 @@
+use std::{cell::Ref, vec::Vec};
+
+use libffi::{low, middle::Type, raw::ffi_get_struct_offsets};
+use mlua::prelude::*;
+
+use super::{association_names::CSTRUCT_INNER, helper, method_provider};
+use crate::ffi::{
+    association, libffi_helper::ffi_status_assert, FfiConvert, FfiData, FfiSignedness, FfiSize,
+};
+
+pub struct CStructInfo {
+    middle_type: Type,
+    size: usize,
+    inner_offset_list: Vec<usize>,
+    inner_conv_list: Vec<*const dyn FfiConvert>,
+}
+
+fn get_field_table<'lua>(
+    lua: &'lua Lua,
+    userdata: &LuaAnyUserData<'lua>,
+) -> LuaResult<LuaTable<'lua>> {
+    let value = association::get(lua, CSTRUCT_INNER, userdata)?
+        .ok_or_else(|| LuaError::external("Failed to retrieve inner field table: not found"))?;
+    if let LuaValue::Table(table) = value {
+        Ok(table)
+    } else {
+        Err(LuaError::external(
+            "Failed to retrieve inner field: not a table",
+        ))
+    }
+}
+
+impl CStructInfo {
+    pub fn new(fields: Vec<Type>, inner_conv_list: Vec<*const dyn FfiConvert>) -> LuaResult<Self> {
+        let len = fields.len();
+        let mut inner_offset_list = Vec::<usize>::with_capacity(len);
+        let middle_type = Type::structure(fields);
+
+        // Get field offsets with ffi_get_struct_offsets
+        unsafe {
+            ffi_status_assert(ffi_get_struct_offsets(
+                low::ffi_abi_FFI_DEFAULT_ABI,
+                middle_type.as_raw_ptr(),
+                inner_offset_list.as_mut_ptr(),
+            ))?;
+            inner_offset_list.set_len(len);
+        }
+
+        // Get tailing padded size of struct (get_ensured_size not required)
+        let size = unsafe { (*middle_type.as_raw_ptr()).size };
+
+        Ok(Self {
+            middle_type,
+            size,
+            inner_offset_list,
+            inner_conv_list,
+        })
+    }
+
+    // Create new CStruct from LuaTable.
+    // Freeze and hold table
+    pub fn from_table<'lua>(
+        lua: &'lua Lua,
+        table: LuaTable<'lua>,
+    ) -> LuaResult<LuaAnyUserData<'lua>> {
+        if helper::has_void(&table)? {
+            return Err(LuaError::external("Void field in sturct is not allowed"));
+        }
+
+        let cstruct = lua
+            .create_userdata(Self::new(helper::get_middle_type_list(&table)?, unsafe {
+                helper::get_conv_list(&table)?
+            })?)?;
+
+        // Save field table
+        table.set_readonly(true);
+        association::set(lua, CSTRUCT_INNER, &cstruct, table)?;
+        Ok(cstruct)
+    }
+
+    // Stringify cstruct for pretty printing
+    // ex: <CStruct( u8, i32, size = 8 )>
+    pub fn stringify(lua: &Lua, userdata: &LuaAnyUserData) -> LuaResult<String> {
+        let fields = get_field_table(lua, userdata)?;
+        let mut stringified = String::from(" ");
+
+        // Children
+        for i in 0..fields.raw_len() {
+            let child: LuaAnyUserData = fields.raw_get(i + 1)?;
+            let pretty_formatted = helper::pretty_format(lua, &child)?;
+            stringified.push_str(format!("{pretty_formatted}, ").as_str());
+        }
+
+        // Size
+        stringified
+            .push_str(format!("size = {} ", userdata.borrow::<CStructInfo>()?.get_size()).as_str());
+        Ok(stringified)
+    }
+
+    // Get byte offset of nth field
+    pub fn offset(&self, index: usize) -> LuaResult<usize> {
+        Ok(self
+            .inner_offset_list
+            .get(index)
+            .ok_or_else(|| LuaError::external("Out of index"))?
+            .to_owned())
+    }
+
+    pub fn get_middle_type(&self) -> Type {
+        self.middle_type.clone()
+    }
+}
+
+impl FfiSize for CStructInfo {
+    fn get_size(&self) -> usize {
+        self.size
+    }
+}
+
+impl FfiSignedness for CStructInfo {
+    fn get_signedness(&self) -> bool {
+        false
+    }
+}
+
+impl FfiConvert for CStructInfo {
+    unsafe fn value_into_data<'lua>(
+        &self,
+        lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+        value: LuaValue<'lua>,
+    ) -> LuaResult<()> {
+        let LuaValue::Table(ref table) = value else {
+            return Err(LuaError::external("Value is not a table"));
+        };
+        for (index, conv) in self.inner_conv_list.iter().enumerate() {
+            let field_offset = self.offset(index)? as isize;
+            let data: LuaValue = table.get(index + 1)?;
+            conv.as_ref().unwrap().value_into_data(
+                lua,
+                field_offset + offset,
+                data_handle,
+                data,
+            )?;
+        }
+        Ok(())
+    }
+
+    unsafe fn value_from_data<'lua>(
+        &self,
+        lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<LuaValue<'lua>> {
+        let table = lua.create_table_with_capacity(self.inner_conv_list.len(), 0)?;
+        for (i, conv) in self.inner_conv_list.iter().enumerate() {
+            let field_offset = self.offset(i)? as isize;
+            table.set(
+                i + 1,
+                conv.as_ref()
+                    .unwrap()
+                    .value_from_data(lua, field_offset + offset, data_handle)?,
+            )?;
+        }
+        Ok(LuaValue::Table(table))
+    }
+    unsafe fn copy_data(
+        &self,
+        _lua: &Lua,
+        dst_offset: isize,
+        src_offset: isize,
+        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(),
+        );
+        Ok(())
+    }
+}
+
+impl LuaUserData for CStructInfo {
+    fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
+        fields.add_field_method_get("size", |_lua, this| Ok(this.get_size()));
+    }
+    fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
+        // Subtype
+        method_provider::provide_ptr(methods);
+        method_provider::provide_arr(methods);
+
+        // ToString
+        method_provider::provide_to_string(methods);
+
+        // Realize
+        method_provider::provide_box(methods);
+        method_provider::provide_read_data(methods);
+        method_provider::provide_write_data(methods);
+        method_provider::provide_copy_data(methods);
+
+        // Get nth field offset
+        methods.add_method("offset", |_lua, this, index: usize| this.offset(index));
+        // Get nth field type
+        methods.add_function(
+            "field",
+            |lua, (this, field_index): (LuaAnyUserData, usize)| {
+                let field_table = get_field_table(lua, &this)?;
+                field_table
+                    .raw_get::<_, Option<LuaAnyUserData>>(field_index + 1)?
+                    .ok_or_else(|| LuaError::external("Out of index"))
+            },
+        );
+    }
+}
diff --git a/crates/lune-std-ffi/src/c/type_info.rs b/crates/lune-std-ffi/src/c/type_info.rs
new file mode 100644
index 0000000..5da8f60
--- /dev/null
+++ b/crates/lune-std-ffi/src/c/type_info.rs
@@ -0,0 +1,142 @@
+#![allow(clippy::inline_always)]
+
+use std::{cell::Ref, marker::PhantomData};
+
+use libffi::middle::Type;
+use lune_utils::fmt::{pretty_format_value, ValueFormatConfig};
+use mlua::prelude::*;
+
+use super::method_provider;
+use crate::{
+    data::GetFfiData,
+    ffi::{libffi_helper::get_ensured_size, FfiConvert, FfiData, FfiSignedness, FfiSize},
+};
+
+// Provide type casting
+// This trait should be implemented for each types
+pub trait CTypeCast {
+    #[inline(always)]
+    fn cast(
+        &self,
+        from_ctype: &LuaAnyUserData,
+        into_ctype: &LuaAnyUserData,
+        _from: &Ref<dyn FfiData>,
+        _into: &Ref<dyn FfiData>,
+        _from_offset: isize,
+        _into_offset: isize,
+    ) -> LuaResult<()> {
+        // Error if have no cast implement
+        Err(Self::cast_failed_with(self, from_ctype, into_ctype))
+    }
+
+    fn cast_failed_with(
+        &self,
+        from_ctype: &LuaAnyUserData,
+        into_ctype: &LuaAnyUserData,
+    ) -> LuaError {
+        let config = ValueFormatConfig::new();
+        LuaError::external(format!(
+            "Failed to cast {} into {}",
+            pretty_format_value(&LuaValue::UserData(from_ctype.to_owned()), &config),
+            pretty_format_value(&LuaValue::UserData(into_ctype.to_owned()), &config),
+        ))
+    }
+}
+
+pub struct CTypeInfo<T> {
+    middle_type: Type,
+    size: usize,
+    name: &'static str,
+    _phantom: PhantomData<T>,
+}
+
+impl<T> FfiSize for CTypeInfo<T> {
+    fn get_size(&self) -> usize {
+        self.size
+    }
+}
+
+impl<T> CTypeInfo<T>
+where
+    T: 'static,
+    Self: CTypeCast + FfiSignedness + FfiConvert + FfiSize,
+{
+    pub fn from_middle_type<'lua>(
+        lua: &'lua Lua,
+        libffi_type: Type,
+        name: &'static str,
+    ) -> LuaResult<LuaAnyUserData<'lua>> {
+        let size = get_ensured_size(libffi_type.as_raw_ptr())?;
+
+        let ctype = Self {
+            middle_type: libffi_type,
+            size,
+            name,
+            _phantom: PhantomData,
+        };
+        let userdata = lua.create_userdata(ctype)?;
+
+        Ok(userdata)
+    }
+
+    pub fn get_name(&self) -> &'static str {
+        self.name
+    }
+
+    pub fn get_middle_type(&self) -> Type {
+        self.middle_type.clone()
+    }
+}
+
+impl<T> LuaUserData for CTypeInfo<T>
+where
+    T: 'static,
+    Self: CTypeCast + FfiSignedness + FfiConvert + FfiSize,
+{
+    fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
+        fields.add_meta_field(LuaMetaMethod::Type, "CTypeInfo");
+        fields.add_field_method_get("size", |_lua, this| Ok(this.get_size()));
+        fields.add_field_method_get("signedness", |_lua, this| Ok(this.get_signedness()));
+    }
+
+    fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
+        // Subtype
+        method_provider::provide_ptr(methods);
+        method_provider::provide_arr(methods);
+
+        // ToString
+        method_provider::provide_to_string(methods);
+
+        // Realize
+        method_provider::provide_box(methods);
+        method_provider::provide_read_data(methods);
+        method_provider::provide_write_data(methods);
+        method_provider::provide_copy_data(methods);
+        method_provider::provide_stringify_data(methods);
+
+        // Math
+        // TODO: Math support for numeric types
+
+        methods.add_function(
+            "cast",
+            |_lua,
+             (from_type, into_type, from, into, from_offset, into_offset): (
+                LuaAnyUserData,
+                LuaAnyUserData,
+                LuaAnyUserData,
+                LuaAnyUserData,
+                Option<isize>,
+                Option<isize>,
+            )| {
+                from_type.borrow::<Self>()?.cast(
+                    &from_type,
+                    &into_type,
+                    &from.get_ffi_data()?,
+                    &into.get_ffi_data()?,
+                    from_offset.unwrap_or(0),
+                    into_offset.unwrap_or(0),
+                )
+            },
+        );
+    }
+}
diff --git a/crates/lune-std-ffi/src/c/types/f32.rs b/crates/lune-std-ffi/src/c/types/f32.rs
new file mode 100644
index 0000000..b13fb4a
--- /dev/null
+++ b/crates/lune-std-ffi/src/c/types/f32.rs
@@ -0,0 +1,90 @@
+use std::cell::Ref;
+
+use mlua::prelude::*;
+use num::cast::AsPrimitive;
+
+use crate::{
+    c::type_info::CTypeInfo,
+    ffi::{FfiConvert, FfiData, FfiSignedness},
+};
+
+impl FfiSignedness for CTypeInfo<f32> {
+    fn get_signedness(&self) -> bool {
+        true
+    }
+}
+
+impl FfiConvert for CTypeInfo<f32> {
+    unsafe fn value_into_data<'lua>(
+        &self,
+        _lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+        value: LuaValue<'lua>,
+    ) -> LuaResult<()> {
+        let value: f32 = match value {
+            LuaValue::Integer(t) => t.as_(),
+            LuaValue::Number(t) => t.as_(),
+            LuaValue::String(t) => t
+                .to_string_lossy()
+                .parse::<f32>()
+                .map_err(LuaError::external)?,
+            _ => {
+                return Err(LuaError::external(format!(
+                    "Value must be a Integer, Number or String, got {}",
+                    value.type_name()
+                )))
+            }
+        };
+        unsafe {
+            *(data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<f32>()) = value;
+        }
+        Ok(())
+    }
+    unsafe fn value_from_data<'lua>(
+        &self,
+        lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<LuaValue<'lua>> {
+        let value = unsafe {
+            (*data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<f32>())
+            .into_lua(lua)?
+        };
+        Ok(value)
+    }
+    unsafe fn copy_data(
+        &self,
+        _lua: &Lua,
+        dst_offset: isize,
+        src_offset: isize,
+        dst: &Ref<dyn FfiData>,
+        src: &Ref<dyn FfiData>,
+    ) -> LuaResult<()> {
+        *dst.get_inner_pointer()
+            .byte_offset(dst_offset)
+            .cast::<f32>() = *src
+            .get_inner_pointer()
+            .byte_offset(src_offset)
+            .cast::<f32>();
+        Ok(())
+    }
+    unsafe fn stringify_data(
+        &self,
+        _lua: &Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<String> {
+        Ok((*data_handle
+            .get_inner_pointer()
+            .byte_offset(offset)
+            .cast::<f32>())
+        .to_string())
+    }
+}
diff --git a/crates/lune-std-ffi/src/c/types/f64.rs b/crates/lune-std-ffi/src/c/types/f64.rs
new file mode 100644
index 0000000..1747e39
--- /dev/null
+++ b/crates/lune-std-ffi/src/c/types/f64.rs
@@ -0,0 +1,90 @@
+use std::cell::Ref;
+
+use mlua::prelude::*;
+use num::cast::AsPrimitive;
+
+use crate::{
+    c::type_info::CTypeInfo,
+    ffi::{FfiConvert, FfiData, FfiSignedness},
+};
+
+impl FfiSignedness for CTypeInfo<f64> {
+    fn get_signedness(&self) -> bool {
+        true
+    }
+}
+
+impl FfiConvert for CTypeInfo<f64> {
+    unsafe fn value_into_data<'lua>(
+        &self,
+        _lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+        value: LuaValue<'lua>,
+    ) -> LuaResult<()> {
+        let value: f64 = match value {
+            LuaValue::Integer(t) => t.as_(),
+            LuaValue::Number(t) => t.as_(),
+            LuaValue::String(t) => t
+                .to_string_lossy()
+                .parse::<f64>()
+                .map_err(LuaError::external)?,
+            _ => {
+                return Err(LuaError::external(format!(
+                    "Value must be a Integer, Number or String, got {}",
+                    value.type_name()
+                )))
+            }
+        };
+        unsafe {
+            *(data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<f64>()) = value;
+        }
+        Ok(())
+    }
+    unsafe fn value_from_data<'lua>(
+        &self,
+        lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<LuaValue<'lua>> {
+        let value = unsafe {
+            (*data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<f64>())
+            .into_lua(lua)?
+        };
+        Ok(value)
+    }
+    unsafe fn copy_data(
+        &self,
+        _lua: &Lua,
+        dst_offset: isize,
+        src_offset: isize,
+        dst: &Ref<dyn FfiData>,
+        src: &Ref<dyn FfiData>,
+    ) -> LuaResult<()> {
+        *dst.get_inner_pointer()
+            .byte_offset(dst_offset)
+            .cast::<f64>() = *src
+            .get_inner_pointer()
+            .byte_offset(src_offset)
+            .cast::<f64>();
+        Ok(())
+    }
+    unsafe fn stringify_data(
+        &self,
+        _lua: &Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<String> {
+        Ok((*data_handle
+            .get_inner_pointer()
+            .byte_offset(offset)
+            .cast::<f64>())
+        .to_string())
+    }
+}
diff --git a/crates/lune-std-ffi/src/c/types/i128.rs b/crates/lune-std-ffi/src/c/types/i128.rs
new file mode 100644
index 0000000..e60b130
--- /dev/null
+++ b/crates/lune-std-ffi/src/c/types/i128.rs
@@ -0,0 +1,90 @@
+use std::cell::Ref;
+
+use mlua::prelude::*;
+use num::cast::AsPrimitive;
+
+use crate::{
+    c::type_info::CTypeInfo,
+    ffi::{FfiConvert, FfiData, FfiSignedness},
+};
+
+impl FfiSignedness for CTypeInfo<i128> {
+    fn get_signedness(&self) -> bool {
+        true
+    }
+}
+
+impl FfiConvert for CTypeInfo<i128> {
+    unsafe fn value_into_data<'lua>(
+        &self,
+        _lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+        value: LuaValue<'lua>,
+    ) -> LuaResult<()> {
+        let value: i128 = match value {
+            LuaValue::Integer(t) => t.as_(),
+            LuaValue::Number(t) => t.as_(),
+            LuaValue::String(t) => t
+                .to_string_lossy()
+                .parse::<i128>()
+                .map_err(LuaError::external)?,
+            _ => {
+                return Err(LuaError::external(format!(
+                    "Value must be a Integer, Number or String, got {}",
+                    value.type_name()
+                )))
+            }
+        };
+        unsafe {
+            *(data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<i128>()) = value;
+        }
+        Ok(())
+    }
+    unsafe fn value_from_data<'lua>(
+        &self,
+        lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<LuaValue<'lua>> {
+        let value = unsafe {
+            (*data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<i128>())
+            .into_lua(lua)?
+        };
+        Ok(value)
+    }
+    unsafe fn copy_data(
+        &self,
+        _lua: &Lua,
+        dst_offset: isize,
+        src_offset: isize,
+        dst: &Ref<dyn FfiData>,
+        src: &Ref<dyn FfiData>,
+    ) -> LuaResult<()> {
+        *dst.get_inner_pointer()
+            .byte_offset(dst_offset)
+            .cast::<i128>() = *src
+            .get_inner_pointer()
+            .byte_offset(src_offset)
+            .cast::<i128>();
+        Ok(())
+    }
+    unsafe fn stringify_data(
+        &self,
+        _lua: &Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<String> {
+        Ok((*data_handle
+            .get_inner_pointer()
+            .byte_offset(offset)
+            .cast::<i128>())
+        .to_string())
+    }
+}
diff --git a/crates/lune-std-ffi/src/c/types/i16.rs b/crates/lune-std-ffi/src/c/types/i16.rs
new file mode 100644
index 0000000..e82920f
--- /dev/null
+++ b/crates/lune-std-ffi/src/c/types/i16.rs
@@ -0,0 +1,90 @@
+use std::cell::Ref;
+
+use mlua::prelude::*;
+use num::cast::AsPrimitive;
+
+use crate::{
+    c::type_info::CTypeInfo,
+    ffi::{FfiConvert, FfiData, FfiSignedness},
+};
+
+impl FfiSignedness for CTypeInfo<i16> {
+    fn get_signedness(&self) -> bool {
+        true
+    }
+}
+
+impl FfiConvert for CTypeInfo<i16> {
+    unsafe fn value_into_data<'lua>(
+        &self,
+        _lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+        value: LuaValue<'lua>,
+    ) -> LuaResult<()> {
+        let value: i16 = match value {
+            LuaValue::Integer(t) => t.as_(),
+            LuaValue::Number(t) => t.as_(),
+            LuaValue::String(t) => t
+                .to_string_lossy()
+                .parse::<i16>()
+                .map_err(LuaError::external)?,
+            _ => {
+                return Err(LuaError::external(format!(
+                    "Value must be a Integer, Number or String, got {}",
+                    value.type_name()
+                )))
+            }
+        };
+        unsafe {
+            *(data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<i16>()) = value;
+        }
+        Ok(())
+    }
+    unsafe fn value_from_data<'lua>(
+        &self,
+        lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<LuaValue<'lua>> {
+        let value = unsafe {
+            (*data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<i16>())
+            .into_lua(lua)?
+        };
+        Ok(value)
+    }
+    unsafe fn copy_data(
+        &self,
+        _lua: &Lua,
+        dst_offset: isize,
+        src_offset: isize,
+        dst: &Ref<dyn FfiData>,
+        src: &Ref<dyn FfiData>,
+    ) -> LuaResult<()> {
+        *dst.get_inner_pointer()
+            .byte_offset(dst_offset)
+            .cast::<i16>() = *src
+            .get_inner_pointer()
+            .byte_offset(src_offset)
+            .cast::<i16>();
+        Ok(())
+    }
+    unsafe fn stringify_data(
+        &self,
+        _lua: &Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<String> {
+        Ok((*data_handle
+            .get_inner_pointer()
+            .byte_offset(offset)
+            .cast::<i16>())
+        .to_string())
+    }
+}
diff --git a/crates/lune-std-ffi/src/c/types/i32.rs b/crates/lune-std-ffi/src/c/types/i32.rs
new file mode 100644
index 0000000..b25515a
--- /dev/null
+++ b/crates/lune-std-ffi/src/c/types/i32.rs
@@ -0,0 +1,90 @@
+use std::cell::Ref;
+
+use mlua::prelude::*;
+use num::cast::AsPrimitive;
+
+use crate::{
+    c::type_info::CTypeInfo,
+    ffi::{FfiConvert, FfiData, FfiSignedness},
+};
+
+impl FfiSignedness for CTypeInfo<i32> {
+    fn get_signedness(&self) -> bool {
+        true
+    }
+}
+
+impl FfiConvert for CTypeInfo<i32> {
+    unsafe fn value_into_data<'lua>(
+        &self,
+        _lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+        value: LuaValue<'lua>,
+    ) -> LuaResult<()> {
+        let value: i32 = match value {
+            LuaValue::Integer(t) => t.as_(),
+            LuaValue::Number(t) => t.as_(),
+            LuaValue::String(t) => t
+                .to_string_lossy()
+                .parse::<i32>()
+                .map_err(LuaError::external)?,
+            _ => {
+                return Err(LuaError::external(format!(
+                    "Value must be a Integer, Number or String, got {}",
+                    value.type_name()
+                )))
+            }
+        };
+        unsafe {
+            *(data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<i32>()) = value;
+        }
+        Ok(())
+    }
+    unsafe fn value_from_data<'lua>(
+        &self,
+        lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<LuaValue<'lua>> {
+        let value = unsafe {
+            (*data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<i32>())
+            .into_lua(lua)?
+        };
+        Ok(value)
+    }
+    unsafe fn copy_data(
+        &self,
+        _lua: &Lua,
+        dst_offset: isize,
+        src_offset: isize,
+        dst: &Ref<dyn FfiData>,
+        src: &Ref<dyn FfiData>,
+    ) -> LuaResult<()> {
+        *dst.get_inner_pointer()
+            .byte_offset(dst_offset)
+            .cast::<i32>() = *src
+            .get_inner_pointer()
+            .byte_offset(src_offset)
+            .cast::<i32>();
+        Ok(())
+    }
+    unsafe fn stringify_data(
+        &self,
+        _lua: &Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<String> {
+        Ok((*data_handle
+            .get_inner_pointer()
+            .byte_offset(offset)
+            .cast::<i32>())
+        .to_string())
+    }
+}
diff --git a/crates/lune-std-ffi/src/c/types/i64.rs b/crates/lune-std-ffi/src/c/types/i64.rs
new file mode 100644
index 0000000..db4e567
--- /dev/null
+++ b/crates/lune-std-ffi/src/c/types/i64.rs
@@ -0,0 +1,90 @@
+use std::cell::Ref;
+
+use mlua::prelude::*;
+use num::cast::AsPrimitive;
+
+use crate::{
+    c::type_info::CTypeInfo,
+    ffi::{FfiConvert, FfiData, FfiSignedness},
+};
+
+impl FfiSignedness for CTypeInfo<i64> {
+    fn get_signedness(&self) -> bool {
+        true
+    }
+}
+
+impl FfiConvert for CTypeInfo<i64> {
+    unsafe fn value_into_data<'lua>(
+        &self,
+        _lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+        value: LuaValue<'lua>,
+    ) -> LuaResult<()> {
+        let value: i64 = match value {
+            LuaValue::Integer(t) => t.as_(),
+            LuaValue::Number(t) => t.as_(),
+            LuaValue::String(t) => t
+                .to_string_lossy()
+                .parse::<i64>()
+                .map_err(LuaError::external)?,
+            _ => {
+                return Err(LuaError::external(format!(
+                    "Value must be a Integer, Number or String, got {}",
+                    value.type_name()
+                )))
+            }
+        };
+        unsafe {
+            *(data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<i64>()) = value;
+        }
+        Ok(())
+    }
+    unsafe fn value_from_data<'lua>(
+        &self,
+        lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<LuaValue<'lua>> {
+        let value = unsafe {
+            (*data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<i64>())
+            .into_lua(lua)?
+        };
+        Ok(value)
+    }
+    unsafe fn copy_data(
+        &self,
+        _lua: &Lua,
+        dst_offset: isize,
+        src_offset: isize,
+        dst: &Ref<dyn FfiData>,
+        src: &Ref<dyn FfiData>,
+    ) -> LuaResult<()> {
+        *dst.get_inner_pointer()
+            .byte_offset(dst_offset)
+            .cast::<i64>() = *src
+            .get_inner_pointer()
+            .byte_offset(src_offset)
+            .cast::<i64>();
+        Ok(())
+    }
+    unsafe fn stringify_data(
+        &self,
+        _lua: &Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<String> {
+        Ok((*data_handle
+            .get_inner_pointer()
+            .byte_offset(offset)
+            .cast::<i64>())
+        .to_string())
+    }
+}
diff --git a/crates/lune-std-ffi/src/c/types/i8.rs b/crates/lune-std-ffi/src/c/types/i8.rs
new file mode 100644
index 0000000..4384f74
--- /dev/null
+++ b/crates/lune-std-ffi/src/c/types/i8.rs
@@ -0,0 +1,82 @@
+use std::cell::Ref;
+
+use mlua::prelude::*;
+use num::cast::AsPrimitive;
+
+use crate::{
+    c::type_info::CTypeInfo,
+    ffi::{FfiConvert, FfiData, FfiSignedness},
+};
+
+impl FfiSignedness for CTypeInfo<i8> {
+    fn get_signedness(&self) -> bool {
+        true
+    }
+}
+
+impl FfiConvert for CTypeInfo<i8> {
+    unsafe fn value_into_data<'lua>(
+        &self,
+        _lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+        value: LuaValue<'lua>,
+    ) -> LuaResult<()> {
+        let value: i8 = match value {
+            LuaValue::Integer(t) => t.as_(),
+            LuaValue::String(t) => t.as_bytes().first().map_or(0, u8::to_owned).as_(),
+            _ => {
+                return Err(LuaError::external(format!(
+                    "Value must be a Integer or String, got {}",
+                    value.type_name()
+                )))
+            }
+        };
+        unsafe {
+            *(data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<i8>()) = value;
+        }
+        Ok(())
+    }
+    unsafe fn value_from_data<'lua>(
+        &self,
+        lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<LuaValue<'lua>> {
+        let value = unsafe {
+            (*data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<i8>())
+            .into_lua(lua)?
+        };
+        Ok(value)
+    }
+    unsafe fn copy_data(
+        &self,
+        _lua: &Lua,
+        dst_offset: isize,
+        src_offset: isize,
+        dst: &Ref<dyn FfiData>,
+        src: &Ref<dyn FfiData>,
+    ) -> LuaResult<()> {
+        *dst.get_inner_pointer().byte_offset(dst_offset).cast::<i8>() =
+            *src.get_inner_pointer().byte_offset(src_offset).cast::<i8>();
+        Ok(())
+    }
+    unsafe fn stringify_data(
+        &self,
+        _lua: &Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<String> {
+        Ok((*data_handle
+            .get_inner_pointer()
+            .byte_offset(offset)
+            .cast::<i8>())
+        .to_string())
+    }
+}
diff --git a/crates/lune-std-ffi/src/c/types/isize.rs b/crates/lune-std-ffi/src/c/types/isize.rs
new file mode 100644
index 0000000..bee4db6
--- /dev/null
+++ b/crates/lune-std-ffi/src/c/types/isize.rs
@@ -0,0 +1,90 @@
+use std::cell::Ref;
+
+use mlua::prelude::*;
+use num::cast::AsPrimitive;
+
+use crate::{
+    c::type_info::CTypeInfo,
+    ffi::{FfiConvert, FfiData, FfiSignedness},
+};
+
+impl FfiSignedness for CTypeInfo<isize> {
+    fn get_signedness(&self) -> bool {
+        true
+    }
+}
+
+impl FfiConvert for CTypeInfo<isize> {
+    unsafe fn value_into_data<'lua>(
+        &self,
+        _lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+        value: LuaValue<'lua>,
+    ) -> LuaResult<()> {
+        let value: isize = match value {
+            LuaValue::Integer(t) => t.as_(),
+            LuaValue::Number(t) => t.as_(),
+            LuaValue::String(t) => t
+                .to_string_lossy()
+                .parse::<isize>()
+                .map_err(LuaError::external)?,
+            _ => {
+                return Err(LuaError::external(format!(
+                    "Value must be a Integer, Number or String, got {}",
+                    value.type_name()
+                )))
+            }
+        };
+        unsafe {
+            *(data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<isize>()) = value;
+        }
+        Ok(())
+    }
+    unsafe fn value_from_data<'lua>(
+        &self,
+        lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<LuaValue<'lua>> {
+        let value = unsafe {
+            (*data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<isize>())
+            .into_lua(lua)?
+        };
+        Ok(value)
+    }
+    unsafe fn copy_data(
+        &self,
+        _lua: &Lua,
+        dst_offset: isize,
+        src_offset: isize,
+        dst: &Ref<dyn FfiData>,
+        src: &Ref<dyn FfiData>,
+    ) -> LuaResult<()> {
+        *dst.get_inner_pointer()
+            .byte_offset(dst_offset)
+            .cast::<isize>() = *src
+            .get_inner_pointer()
+            .byte_offset(src_offset)
+            .cast::<isize>();
+        Ok(())
+    }
+    unsafe fn stringify_data(
+        &self,
+        _lua: &Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<String> {
+        Ok((*data_handle
+            .get_inner_pointer()
+            .byte_offset(offset)
+            .cast::<isize>())
+        .to_string())
+    }
+}
diff --git a/crates/lune-std-ffi/src/c/types/mod.rs b/crates/lune-std-ffi/src/c/types/mod.rs
new file mode 100644
index 0000000..caa2a62
--- /dev/null
+++ b/crates/lune-std-ffi/src/c/types/mod.rs
@@ -0,0 +1,203 @@
+#![allow(clippy::inline_always)]
+
+use core::ffi::*;
+use std::{any::TypeId, cell::Ref};
+
+use libffi::middle::Type;
+use mlua::prelude::*;
+use num::cast::AsPrimitive;
+
+use super::{CTypeCast, CTypeInfo};
+use crate::ffi::{num_cast, FfiConvert, FfiData, FfiSize};
+
+mod f32;
+mod f64;
+mod i128;
+mod i16;
+mod i32;
+mod i64;
+mod i8;
+mod isize;
+mod u128;
+mod u16;
+mod u32;
+mod u64;
+mod u8;
+mod usize;
+
+// CType userdata export
+macro_rules! create_ctypes {
+    ($lua:ident, $(( $name:expr, $rust_type:ty, $libffi_type:expr ),)* ) => {
+        Ok(vec![$((
+            $name,
+            CTypeInfo::<$rust_type>::from_middle_type($lua, $libffi_type, $name)?,
+        ),)*])
+    };
+}
+pub fn export_c_types(lua: &Lua) -> LuaResult<Vec<(&'static str, LuaAnyUserData)>> {
+    create_ctypes!(
+        lua,
+        // Export Compile-time known c-types
+        ("char", c_char, {
+            if TypeId::of::<c_char>() == TypeId::of::<u8>() {
+                Type::c_uchar()
+            } else {
+                Type::c_schar()
+            }
+        }),
+        ("uchar", c_uchar, Type::c_uchar()),
+        ("schar", c_schar, Type::c_schar()),
+        ("short", c_short, Type::c_short()),
+        ("ushort", c_ushort, Type::c_ushort()),
+        ("int", c_int, Type::c_int()),
+        ("uint", c_uint, Type::c_uint()),
+        ("long", c_long, Type::c_long()),
+        ("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()),
+        ("u32", u32, Type::u32()),
+        ("u64", u64, Type::u64()),
+        ("u128", u128, Type::c_longlong()),
+        ("i8", i8, Type::i8()),
+        ("i16", i16, Type::i16()),
+        ("i32", i32, Type::i32()),
+        ("i64", i64, Type::i64()),
+        ("i128", i128, Type::c_ulonglong()),
+        ("f64", f64, Type::f64()),
+        ("f32", f32, Type::f32()),
+        ("usize", usize, Type::usize()),
+        ("isize", isize, Type::isize()),
+        ("f32", f32, Type::f32()),
+        ("f64", f64, Type::f64()),
+    )
+}
+
+// Implement type-casting for numeric ctypes
+macro_rules! define_cast_num {
+    ($from_rust_type:ident, $self:ident, $from_ctype:ident, $into_ctype:ident, $from:ident, $into:ident, $fromOffset:ident, $intoOffset:ident, $($into_rust_type:ty)*) => {
+        $( if $into_ctype.is::<CTypeInfo<$into_rust_type>>() {
+            num_cast::<$from_rust_type, $into_rust_type>($from, $into, $fromOffset, $intoOffset)
+        } else )* {
+            Err($self.cast_failed_with($from_ctype, $into_ctype))
+        }
+    };
+}
+impl<From> CTypeCast for CTypeInfo<From>
+where
+    From: AsPrimitive<f32>
+        + AsPrimitive<f64>
+        + AsPrimitive<i128>
+        + AsPrimitive<i16>
+        + AsPrimitive<i32>
+        + AsPrimitive<i64>
+        + AsPrimitive<i8>
+        + AsPrimitive<isize>
+        + AsPrimitive<u128>
+        + AsPrimitive<u16>
+        + AsPrimitive<u32>
+        + AsPrimitive<u64>
+        + AsPrimitive<u8>
+        + AsPrimitive<usize>,
+{
+    fn cast(
+        &self,
+        from_info: &LuaAnyUserData,
+        into_info: &LuaAnyUserData,
+        from: &Ref<dyn FfiData>,
+        into: &Ref<dyn FfiData>,
+        from_offset: isize,
+        into_offset: isize,
+    ) -> LuaResult<()> {
+        define_cast_num!(
+            From, self, from_info, into_info, from, into, from_offset, into_offset,
+            f32 f64 i128 i16 i32 i64 i8 isize u128 u16 u32 u64 u8 usize
+        )
+    }
+}
+
+pub mod ctype_helper {
+    use super::*;
+
+    // To prevent droping NativeConvert, need to ensure userdata keep alive
+    macro_rules! define_get_conv {
+        ($userdata:ident, $( $rust_type:ty )*) => {
+            $( if $userdata.is::<CTypeInfo<$rust_type>>() {
+                Ok($userdata.to_pointer().cast::<CTypeInfo<$rust_type>>() as *const dyn FfiConvert)
+            } else )* {
+                Err(LuaError::external("Unexpected type"))
+            }
+        };
+    }
+    #[inline]
+    pub fn get_conv(userdata: &LuaAnyUserData) -> LuaResult<*const dyn FfiConvert> {
+        define_get_conv!(userdata, f32 f64 i128 i16 i32 i64 i8 isize u128 u16 u32 u64 u8 usize)
+    }
+
+    // Get libffi_type of ctype
+    macro_rules! define_get_middle_type {
+        ($userdata:ident, $( $rust_type:ty )*) => {
+            $( if $userdata.is::<CTypeInfo<$rust_type>>() {
+                Ok(Some($userdata.borrow::<CTypeInfo<$rust_type>>()?.get_middle_type()))
+            } else )* {
+                Ok(None)
+            }
+        };
+    }
+    #[inline]
+    pub fn get_middle_type(userdata: &LuaAnyUserData) -> LuaResult<Option<Type>> {
+        define_get_middle_type!(userdata, f32 f64 i128 i16 i32 i64 i8 isize u128 u16 u32 u64 u8 usize)
+    }
+
+    // Get size of ctype (not including struct, arr, ... only CType<*>)
+    macro_rules! define_get_size {
+        ($userdata:ident, $( $rust_type:ty )*) => {
+            $( if $userdata.is::<CTypeInfo<$rust_type>>() {
+                Ok($userdata.borrow::<CTypeInfo<$rust_type>>()?.get_size())
+            } else )* {
+                Err(LuaError::external("Unexpected type"))
+            }
+        };
+    }
+    #[inline]
+    pub fn get_size(userdata: &LuaAnyUserData) -> LuaResult<usize> {
+        define_get_size!(userdata, f32 f64 i128 i16 i32 i64 i8 isize u128 u16 u32 u64 u8 usize)
+    }
+
+    // Get name of ctype
+    macro_rules! define_get_name {
+        ($userdata:ident, $( $rust_type:ty )*) => {
+            $( if $userdata.is::<CTypeInfo<$rust_type>>() {
+                Ok(Some($userdata.borrow::<CTypeInfo<$rust_type>>()?.get_name()))
+            } else )* {
+                Ok(None)
+            }
+        };
+    }
+    #[inline]
+    pub fn get_name(userdata: &LuaAnyUserData) -> LuaResult<Option<&'static str>> {
+        define_get_name!(userdata, f32 f64 i128 i16 i32 i64 i8 isize u128 u16 u32 u64 u8 usize)
+    }
+
+    // Check whether userdata is ctype or not
+    macro_rules! define_is_ctype {
+        ($userdata:ident, $( $rust_type:ty )*) => {
+            $( if $userdata.is::<CTypeInfo<$rust_type>>() {
+                true
+            } else )* {
+                false
+            }
+        };
+    }
+    #[inline]
+    pub fn is_ctype(userdata: &LuaAnyUserData) -> bool {
+        define_is_ctype!(userdata, f32 f64 i128 i16 i32 i64 i8 isize u128 u16 u32 u64 u8 usize)
+    }
+}
diff --git a/crates/lune-std-ffi/src/c/types/u128.rs b/crates/lune-std-ffi/src/c/types/u128.rs
new file mode 100644
index 0000000..3bb8443
--- /dev/null
+++ b/crates/lune-std-ffi/src/c/types/u128.rs
@@ -0,0 +1,90 @@
+use std::cell::Ref;
+
+use mlua::prelude::*;
+use num::cast::AsPrimitive;
+
+use crate::{
+    c::type_info::CTypeInfo,
+    ffi::{FfiConvert, FfiData, FfiSignedness},
+};
+
+impl FfiSignedness for CTypeInfo<u128> {
+    fn get_signedness(&self) -> bool {
+        false
+    }
+}
+
+impl FfiConvert for CTypeInfo<u128> {
+    unsafe fn value_into_data<'lua>(
+        &self,
+        _lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+        value: LuaValue<'lua>,
+    ) -> LuaResult<()> {
+        let value: u128 = match value {
+            LuaValue::Integer(t) => t.as_(),
+            LuaValue::Number(t) => t.as_(),
+            LuaValue::String(t) => t
+                .to_string_lossy()
+                .parse::<u128>()
+                .map_err(LuaError::external)?,
+            _ => {
+                return Err(LuaError::external(format!(
+                    "Value must be a Integer, Number or String, got {}",
+                    value.type_name()
+                )))
+            }
+        };
+        unsafe {
+            *(data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<u128>()) = value;
+        }
+        Ok(())
+    }
+    unsafe fn value_from_data<'lua>(
+        &self,
+        lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<LuaValue<'lua>> {
+        let value = unsafe {
+            (*data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<u128>())
+            .into_lua(lua)?
+        };
+        Ok(value)
+    }
+    unsafe fn copy_data(
+        &self,
+        _lua: &Lua,
+        dst_offset: isize,
+        src_offset: isize,
+        dst: &Ref<dyn FfiData>,
+        src: &Ref<dyn FfiData>,
+    ) -> LuaResult<()> {
+        *dst.get_inner_pointer()
+            .byte_offset(dst_offset)
+            .cast::<u128>() = *src
+            .get_inner_pointer()
+            .byte_offset(src_offset)
+            .cast::<u128>();
+        Ok(())
+    }
+    unsafe fn stringify_data(
+        &self,
+        _lua: &Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<String> {
+        Ok((*data_handle
+            .get_inner_pointer()
+            .byte_offset(offset)
+            .cast::<u128>())
+        .to_string())
+    }
+}
diff --git a/crates/lune-std-ffi/src/c/types/u16.rs b/crates/lune-std-ffi/src/c/types/u16.rs
new file mode 100644
index 0000000..ec1d072
--- /dev/null
+++ b/crates/lune-std-ffi/src/c/types/u16.rs
@@ -0,0 +1,91 @@
+use std::cell::Ref;
+
+use mlua::prelude::*;
+use num::cast::AsPrimitive;
+
+use crate::{
+    c::type_info::CTypeInfo,
+    ffi::{FfiConvert, FfiData, FfiSignedness},
+};
+
+impl FfiSignedness for CTypeInfo<u16> {
+    fn get_signedness(&self) -> bool {
+        false
+    }
+}
+
+impl FfiConvert for CTypeInfo<u16> {
+    // Convert luavalue into data, then write into ptr
+    unsafe fn value_into_data<'lua>(
+        &self,
+        _lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+        value: LuaValue<'lua>,
+    ) -> LuaResult<()> {
+        let value: u16 = match value {
+            LuaValue::Integer(t) => t.as_(),
+            LuaValue::Number(t) => t.as_(),
+            LuaValue::String(t) => t
+                .to_string_lossy()
+                .parse::<u16>()
+                .map_err(LuaError::external)?,
+            _ => {
+                return Err(LuaError::external(format!(
+                    "Value must be a Integer, Number or String, got {}",
+                    value.type_name()
+                )))
+            }
+        };
+        unsafe {
+            *(data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<u16>()) = value;
+        }
+        Ok(())
+    }
+    unsafe fn value_from_data<'lua>(
+        &self,
+        lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<LuaValue<'lua>> {
+        let value = unsafe {
+            (*data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<u16>())
+            .into_lua(lua)?
+        };
+        Ok(value)
+    }
+    unsafe fn copy_data(
+        &self,
+        _lua: &Lua,
+        dst_offset: isize,
+        src_offset: isize,
+        dst: &Ref<dyn FfiData>,
+        src: &Ref<dyn FfiData>,
+    ) -> LuaResult<()> {
+        *dst.get_inner_pointer()
+            .byte_offset(dst_offset)
+            .cast::<u16>() = *src
+            .get_inner_pointer()
+            .byte_offset(src_offset)
+            .cast::<u16>();
+        Ok(())
+    }
+    unsafe fn stringify_data(
+        &self,
+        _lua: &Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<String> {
+        Ok((*data_handle
+            .get_inner_pointer()
+            .byte_offset(offset)
+            .cast::<u16>())
+        .to_string())
+    }
+}
diff --git a/crates/lune-std-ffi/src/c/types/u32.rs b/crates/lune-std-ffi/src/c/types/u32.rs
new file mode 100644
index 0000000..25942e0
--- /dev/null
+++ b/crates/lune-std-ffi/src/c/types/u32.rs
@@ -0,0 +1,90 @@
+use std::cell::Ref;
+
+use mlua::prelude::*;
+use num::cast::AsPrimitive;
+
+use crate::{
+    c::type_info::CTypeInfo,
+    ffi::{FfiConvert, FfiData, FfiSignedness},
+};
+
+impl FfiSignedness for CTypeInfo<u32> {
+    fn get_signedness(&self) -> bool {
+        false
+    }
+}
+
+impl FfiConvert for CTypeInfo<u32> {
+    unsafe fn value_into_data<'lua>(
+        &self,
+        _lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+        value: LuaValue<'lua>,
+    ) -> LuaResult<()> {
+        let value: u32 = match value {
+            LuaValue::Integer(t) => t.as_(),
+            LuaValue::Number(t) => t.as_(),
+            LuaValue::String(t) => t
+                .to_string_lossy()
+                .parse::<u32>()
+                .map_err(LuaError::external)?,
+            _ => {
+                return Err(LuaError::external(format!(
+                    "Value must be a Integer, Number or String, got {}",
+                    value.type_name()
+                )))
+            }
+        };
+        unsafe {
+            *(data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<u32>()) = value;
+        }
+        Ok(())
+    }
+    unsafe fn value_from_data<'lua>(
+        &self,
+        lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<LuaValue<'lua>> {
+        let value = unsafe {
+            (*data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<u32>())
+            .into_lua(lua)?
+        };
+        Ok(value)
+    }
+    unsafe fn copy_data(
+        &self,
+        _lua: &Lua,
+        dst_offset: isize,
+        src_offset: isize,
+        dst: &Ref<dyn FfiData>,
+        src: &Ref<dyn FfiData>,
+    ) -> LuaResult<()> {
+        *dst.get_inner_pointer()
+            .byte_offset(dst_offset)
+            .cast::<u32>() = *src
+            .get_inner_pointer()
+            .byte_offset(src_offset)
+            .cast::<u32>();
+        Ok(())
+    }
+    unsafe fn stringify_data(
+        &self,
+        _lua: &Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<String> {
+        Ok((*data_handle
+            .get_inner_pointer()
+            .byte_offset(offset)
+            .cast::<f32>())
+        .to_string())
+    }
+}
diff --git a/crates/lune-std-ffi/src/c/types/u64.rs b/crates/lune-std-ffi/src/c/types/u64.rs
new file mode 100644
index 0000000..33d9785
--- /dev/null
+++ b/crates/lune-std-ffi/src/c/types/u64.rs
@@ -0,0 +1,90 @@
+use std::cell::Ref;
+
+use mlua::prelude::*;
+use num::cast::AsPrimitive;
+
+use crate::{
+    c::type_info::CTypeInfo,
+    ffi::{FfiConvert, FfiData, FfiSignedness},
+};
+
+impl FfiSignedness for CTypeInfo<u64> {
+    fn get_signedness(&self) -> bool {
+        false
+    }
+}
+
+impl FfiConvert for CTypeInfo<u64> {
+    unsafe fn value_into_data<'lua>(
+        &self,
+        _lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+        value: LuaValue<'lua>,
+    ) -> LuaResult<()> {
+        let value: u64 = match value {
+            LuaValue::Integer(t) => t.as_(),
+            LuaValue::Number(t) => t.as_(),
+            LuaValue::String(t) => t
+                .to_string_lossy()
+                .parse::<u64>()
+                .map_err(LuaError::external)?,
+            _ => {
+                return Err(LuaError::external(format!(
+                    "Value must be a Integer, Number or String, got {}",
+                    value.type_name()
+                )))
+            }
+        };
+        unsafe {
+            *(data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<u64>()) = value;
+        }
+        Ok(())
+    }
+    unsafe fn value_from_data<'lua>(
+        &self,
+        lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<LuaValue<'lua>> {
+        let value = unsafe {
+            (*data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<u64>())
+            .into_lua(lua)?
+        };
+        Ok(value)
+    }
+    unsafe fn copy_data(
+        &self,
+        _lua: &Lua,
+        dst_offset: isize,
+        src_offset: isize,
+        dst: &Ref<dyn FfiData>,
+        src: &Ref<dyn FfiData>,
+    ) -> LuaResult<()> {
+        *dst.get_inner_pointer()
+            .byte_offset(dst_offset)
+            .cast::<u64>() = *src
+            .get_inner_pointer()
+            .byte_offset(src_offset)
+            .cast::<u64>();
+        Ok(())
+    }
+    unsafe fn stringify_data(
+        &self,
+        _lua: &Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<String> {
+        Ok((*data_handle
+            .get_inner_pointer()
+            .byte_offset(offset)
+            .cast::<u64>())
+        .to_string())
+    }
+}
diff --git a/crates/lune-std-ffi/src/c/types/u8.rs b/crates/lune-std-ffi/src/c/types/u8.rs
new file mode 100644
index 0000000..8764bfc
--- /dev/null
+++ b/crates/lune-std-ffi/src/c/types/u8.rs
@@ -0,0 +1,85 @@
+use std::cell::Ref;
+
+use mlua::prelude::*;
+use num::cast::AsPrimitive;
+
+use crate::{
+    c::type_info::CTypeInfo,
+    ffi::{FfiConvert, FfiData, FfiSignedness},
+};
+
+impl FfiSignedness for CTypeInfo<u8> {
+    fn get_signedness(&self) -> bool {
+        false
+    }
+}
+
+impl FfiConvert for CTypeInfo<u8> {
+    // Convert luavalue into data, then write into ptr
+    unsafe fn value_into_data<'lua>(
+        &self,
+        _lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+        value: LuaValue<'lua>,
+    ) -> LuaResult<()> {
+        let value: u8 = match value {
+            LuaValue::Integer(t) => t.as_(),
+            LuaValue::String(t) => t.as_bytes().first().map_or(0, u8::to_owned).as_(),
+            _ => {
+                return Err(LuaError::external(format!(
+                    "Value must be a Integer or String, got {}",
+                    value.type_name()
+                )))
+            }
+        };
+        unsafe {
+            *(data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<u8>()) = value;
+        }
+        Ok(())
+    }
+
+    // Read data from ptr, then convert into luavalue
+    unsafe fn value_from_data<'lua>(
+        &self,
+        lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<LuaValue<'lua>> {
+        let value = unsafe {
+            (*data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<u8>())
+            .into_lua(lua)?
+        };
+        Ok(value)
+    }
+    unsafe fn copy_data(
+        &self,
+        _lua: &Lua,
+        dst_offset: isize,
+        src_offset: isize,
+        dst: &Ref<dyn FfiData>,
+        src: &Ref<dyn FfiData>,
+    ) -> LuaResult<()> {
+        *dst.get_inner_pointer().byte_offset(dst_offset).cast::<u8>() =
+            *src.get_inner_pointer().byte_offset(src_offset).cast::<u8>();
+        Ok(())
+    }
+    unsafe fn stringify_data(
+        &self,
+        _lua: &Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<String> {
+        Ok((*data_handle
+            .get_inner_pointer()
+            .byte_offset(offset)
+            .cast::<u8>())
+        .to_string())
+    }
+}
diff --git a/crates/lune-std-ffi/src/c/types/usize.rs b/crates/lune-std-ffi/src/c/types/usize.rs
new file mode 100644
index 0000000..a7902ed
--- /dev/null
+++ b/crates/lune-std-ffi/src/c/types/usize.rs
@@ -0,0 +1,90 @@
+use std::cell::Ref;
+
+use mlua::prelude::*;
+use num::cast::AsPrimitive;
+
+use crate::{
+    c::type_info::CTypeInfo,
+    ffi::{FfiConvert, FfiData, FfiSignedness},
+};
+
+impl FfiSignedness for CTypeInfo<usize> {
+    fn get_signedness(&self) -> bool {
+        false
+    }
+}
+
+impl FfiConvert for CTypeInfo<usize> {
+    unsafe fn value_into_data<'lua>(
+        &self,
+        _lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+        value: LuaValue<'lua>,
+    ) -> LuaResult<()> {
+        let value: usize = match value {
+            LuaValue::Integer(t) => t.as_(),
+            LuaValue::Number(t) => t.as_(),
+            LuaValue::String(t) => t
+                .to_string_lossy()
+                .parse::<usize>()
+                .map_err(LuaError::external)?,
+            _ => {
+                return Err(LuaError::external(format!(
+                    "Value must be a Integer, Number or String, got {}",
+                    value.type_name()
+                )))
+            }
+        };
+        unsafe {
+            *(data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<usize>()) = value;
+        }
+        Ok(())
+    }
+    unsafe fn value_from_data<'lua>(
+        &self,
+        lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<LuaValue<'lua>> {
+        let value = unsafe {
+            (*data_handle
+                .get_inner_pointer()
+                .byte_offset(offset)
+                .cast::<usize>())
+            .into_lua(lua)?
+        };
+        Ok(value)
+    }
+    unsafe fn copy_data(
+        &self,
+        _lua: &Lua,
+        dst_offset: isize,
+        src_offset: isize,
+        dst: &Ref<dyn FfiData>,
+        src: &Ref<dyn FfiData>,
+    ) -> LuaResult<()> {
+        *dst.get_inner_pointer()
+            .byte_offset(dst_offset)
+            .cast::<usize>() = *src
+            .get_inner_pointer()
+            .byte_offset(src_offset)
+            .cast::<usize>();
+        Ok(())
+    }
+    unsafe fn stringify_data(
+        &self,
+        _lua: &Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<String> {
+        Ok((*data_handle
+            .get_inner_pointer()
+            .byte_offset(offset)
+            .cast::<usize>())
+        .to_string())
+    }
+}
diff --git a/crates/lune-std-ffi/src/c/void_info.rs b/crates/lune-std-ffi/src/c/void_info.rs
new file mode 100644
index 0000000..9fd1968
--- /dev/null
+++ b/crates/lune-std-ffi/src/c/void_info.rs
@@ -0,0 +1,42 @@
+use libffi::middle::Type;
+use mlua::prelude::*;
+
+use crate::ffi::{FfiSignedness, FfiSize};
+
+use super::method_provider;
+
+pub struct CVoidInfo();
+
+impl FfiSignedness for CVoidInfo {
+    fn get_signedness(&self) -> bool {
+        false
+    }
+}
+
+impl FfiSize for CVoidInfo {
+    fn get_size(&self) -> usize {
+        0
+    }
+}
+
+impl CVoidInfo {
+    pub fn new() -> Self {
+        Self()
+    }
+    pub fn get_middle_type() -> Type {
+        Type::void()
+    }
+    pub fn stringify() -> LuaResult<String> {
+        Ok(String::from("CVoid"))
+    }
+}
+
+impl LuaUserData for CVoidInfo {
+    fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
+        fields.add_field_method_get("size", |_lua, _this| Ok(0));
+    }
+    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/box_data/flag.rs b/crates/lune-std-ffi/src/data/box_data/flag.rs
new file mode 100644
index 0000000..a983f3b
--- /dev/null
+++ b/crates/lune-std-ffi/src/data/box_data/flag.rs
@@ -0,0 +1,13 @@
+use crate::ffi::bit_field::*;
+
+pub enum BoxFlag {
+    Leaked,
+}
+
+impl BoxFlag {
+    pub const fn value(&self) -> u8 {
+        match self {
+            Self::Leaked => U8_MASK1,
+        }
+    }
+}
diff --git a/crates/lune-std-ffi/src/data/box_data/mod.rs b/crates/lune-std-ffi/src/data/box_data/mod.rs
new file mode 100644
index 0000000..1cdf9b4
--- /dev/null
+++ b/crates/lune-std-ffi/src/data/box_data/mod.rs
@@ -0,0 +1,166 @@
+use std::{
+    alloc::{self, Layout},
+    boxed::Box,
+    mem::ManuallyDrop,
+    ptr,
+};
+
+use mlua::prelude::*;
+
+use super::helper::method_provider;
+use crate::{
+    data::{association_names::REF_INNER, RefBounds, RefData, RefFlag},
+    ffi::{association, bit_field::*, FfiData},
+};
+
+mod flag;
+
+pub use self::flag::BoxFlag;
+
+const FFI_BOX_PRINT_MAX_LENGTH: usize = 1024;
+
+// Reference which created by lua should not be dereferenceable
+const BOX_REF_FLAGS: u8 =
+    RefFlag::Readable.value() | RefFlag::Writable.value() | RefFlag::Offsetable.value();
+
+// Untyped runtime sized memory for luau.
+// This operations are safe, have boundaries check.
+pub struct BoxData {
+    flags: u8,
+    data: ManuallyDrop<Box<[u8]>>,
+}
+
+impl BoxData {
+    pub fn new(size: usize) -> Self {
+        let slice = unsafe {
+            Box::from_raw(ptr::slice_from_raw_parts_mut(
+                alloc::alloc(Layout::array::<u8>(size).unwrap()),
+                size,
+            ))
+        };
+
+        Self {
+            flags: 0,
+            data: ManuallyDrop::new(slice),
+        }
+    }
+
+    // Stringify for pretty-print, with hex format content
+    pub fn stringify(&self) -> String {
+        if self.size() > FFI_BOX_PRINT_MAX_LENGTH * 2 {
+            return String::from("length limit exceed");
+        }
+        let mut buff: String = String::with_capacity(self.size() * 2 + 2);
+        buff.push_str("0x");
+        for value in self.data.iter() {
+            buff.push_str(format!("{:x}", value.to_be()).as_str());
+        }
+        buff
+    }
+
+    pub fn leak(&mut self) {
+        self.flags = u8_set(self.flags, BoxFlag::Leaked.value(), true);
+    }
+
+    // Make FfiRef from box, with boundary check
+    pub fn luaref<'lua>(
+        lua: &'lua Lua,
+        this: LuaAnyUserData<'lua>,
+        offset: Option<isize>,
+    ) -> LuaResult<LuaAnyUserData<'lua>> {
+        let target = this.borrow::<BoxData>()?;
+        let mut bounds = RefBounds::new(0, target.size());
+        let mut ptr = unsafe { target.get_inner_pointer() };
+
+        // Calculate offset
+        if let Some(t) = offset {
+            if !bounds.check_offset(t) {
+                return Err(LuaError::external(format!(
+                    "Offset out of bounds (box.size: {}, got {})",
+                    target.size(),
+                    t
+                )));
+            }
+            ptr = unsafe { ptr.byte_offset(t) };
+            bounds = bounds.offset(t);
+        }
+
+        let luaref = lua.create_userdata(RefData::new(ptr.cast(), BOX_REF_FLAGS, bounds))?;
+
+        // Make box live longer then ref
+        association::set(lua, REF_INNER, &luaref, &this)?;
+
+        Ok(luaref)
+    }
+
+    // Fill with zero
+    pub fn zero(&mut self) {
+        self.data.fill(0);
+    }
+
+    // Get size of box
+    #[inline]
+    pub fn size(&self) -> usize {
+        self.data.len()
+    }
+}
+
+impl Drop for BoxData {
+    fn drop(&mut self) {
+        if u8_test_not(self.flags, BoxFlag::Leaked.value()) {
+            unsafe { ManuallyDrop::drop(&mut self.data) };
+        }
+    }
+}
+
+impl FfiData for BoxData {
+    fn check_inner_boundary(&self, offset: isize, size: usize) -> bool {
+        if offset < 0 {
+            return false;
+        }
+        self.size() - (offset as usize) >= size
+    }
+    #[inline]
+    unsafe fn get_inner_pointer(&self) -> *mut () {
+        self.data.as_ptr().cast_mut().cast::<()>()
+    }
+    fn is_readable(&self) -> bool {
+        true
+    }
+    fn is_writable(&self) -> bool {
+        true
+    }
+}
+
+impl LuaUserData for BoxData {
+    fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
+        fields.add_field_method_get("size", |_lua, this| Ok(this.size()));
+    }
+    fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
+        method_provider::provide_copy_from(methods);
+        method_provider::provide_read_string(methods);
+        method_provider::provide_write_string(methods);
+
+        // For convenience, :zero returns box itself.
+        methods.add_function_mut("zero", |_lua, this: LuaAnyUserData| {
+            this.borrow_mut::<BoxData>()?.zero();
+            Ok(this)
+        });
+        methods.add_function_mut(
+            "leak",
+            |lua, (this, offset): (LuaAnyUserData, Option<isize>)| {
+                this.borrow_mut::<BoxData>()?.leak();
+                BoxData::luaref(lua, this, offset)
+            },
+        );
+        methods.add_function(
+            "ref",
+            |lua, (this, offset): (LuaAnyUserData, Option<isize>)| {
+                BoxData::luaref(lua, this, offset)
+            },
+        );
+        methods.add_meta_method(LuaMetaMethod::ToString, |_lua, this, ()| {
+            Ok(this.stringify())
+        });
+    }
+}
diff --git a/crates/lune-std-ffi/src/data/callable_data.rs b/crates/lune-std-ffi/src/data/callable_data.rs
new file mode 100644
index 0000000..1119a2f
--- /dev/null
+++ b/crates/lune-std-ffi/src/data/callable_data.rs
@@ -0,0 +1,191 @@
+use core::ffi::c_void;
+use std::{
+    mem::{self, MaybeUninit},
+    ptr,
+};
+
+use libffi::{
+    low::{ffi_cif, CodePtr},
+    raw::ffi_call,
+};
+use mlua::prelude::*;
+
+use super::{GetFfiData, RefData};
+use crate::ffi::{FfiArg, FfiData, FfiResult};
+
+// A function pointer that luau can call. it stores libffi cif for calling convention.
+pub struct CallableData {
+    cif: *mut ffi_cif,
+    arg_info_list: Vec<FfiArg>,
+    result_info: FfiResult,
+    code: CodePtr,
+}
+
+const VOID_RESULT_PTR: *mut () = ptr::null_mut();
+const ZERO_SIZE_ARG_PTR: *mut *mut c_void = ptr::null_mut();
+
+// Optimization:
+// Use known size array in stack instead of creating new Vec to eliminate heap allocation
+macro_rules! create_caller {
+    ($len:expr) => {
+        |callable: &CallableData, result: LuaValue, args: LuaMultiValue| unsafe {
+            // Get `rvalue: *mut c_void` result pointer
+            let result_pointer = if callable.result_info.size == 0 {
+                VOID_RESULT_PTR
+            } else {
+                result.get_ffi_data()?.get_inner_pointer()
+            }
+            .cast::<c_void>();
+
+            // Create `avalue: *mut *mut c_void` argument list
+            let mut arg_list: [MaybeUninit<*mut c_void>; $len] = [MaybeUninit::uninit(); $len];
+            for (index, arg) in arg_list.iter_mut().enumerate() {
+                let arg_value = args
+                    .get(index)
+                    .ok_or_else(|| LuaError::external(format!("Argument {index} required")))?
+                    .as_userdata()
+                    .ok_or_else(|| LuaError::external("Argument must be a RefData"))?;
+
+                if let Ok(arg_ref) = arg_value.borrow::<RefData>() {
+                    arg.write(arg_ref.get_inner_pointer().cast::<c_void>());
+                } else {
+                    return Err(LuaError::external("Argument must be a RefData"));
+                }
+            }
+
+            ffi_call(
+                callable.cif,
+                Some(*callable.code.as_safe_fun()),
+                result_pointer,
+                // SAFETY: MaybeUninit<T> has the same layout as `T`, and initialized above
+                mem::transmute::<[MaybeUninit<*mut c_void>; $len], [*mut c_void; $len]>(arg_list)
+                    .as_mut_ptr(),
+            );
+
+            Ok(())
+        }
+    };
+}
+
+// Optimization:
+// Call without arguments
+unsafe fn zero_size_caller(
+    callable: &CallableData,
+    result: LuaValue,
+    _args: LuaMultiValue,
+) -> LuaResult<()> {
+    let result_pointer = if callable.result_info.size == 0 {
+        VOID_RESULT_PTR
+    } else {
+        result.get_ffi_data()?.get_inner_pointer()
+    }
+    .cast::<c_void>();
+
+    ffi_call(
+        callable.cif,
+        Some(*callable.code.as_safe_fun()),
+        result_pointer,
+        ZERO_SIZE_ARG_PTR,
+    );
+
+    Ok(())
+}
+
+// Optimization: sized callers
+type Caller =
+    unsafe fn(callable: &CallableData, result: LuaValue, args: LuaMultiValue) -> LuaResult<()>;
+const SIZED_CALLERS: [Caller; 13] = [
+    zero_size_caller,
+    create_caller!(1),
+    create_caller!(2),
+    create_caller!(3),
+    create_caller!(4),
+    create_caller!(5),
+    create_caller!(6),
+    create_caller!(7),
+    create_caller!(8),
+    create_caller!(9),
+    create_caller!(10),
+    create_caller!(11),
+    create_caller!(12),
+];
+
+impl CallableData {
+    pub unsafe fn new(
+        cif: *mut ffi_cif,
+        arg_info_list: Vec<FfiArg>,
+        result_info: FfiResult,
+        function_pointer: *const (),
+    ) -> Self {
+        Self {
+            cif,
+            arg_info_list,
+            result_info,
+            code: CodePtr::from_ptr(function_pointer.cast::<c_void>()),
+        }
+    }
+
+    // Stringify for pretty-print, with hex format address
+    pub fn stringify(&self) -> String {
+        format!("0x{:x}", self.code.as_ptr() as usize)
+    }
+
+    pub unsafe fn call(&self, result: LuaValue, args: LuaMultiValue) -> LuaResult<()> {
+        let arg_len = self.arg_info_list.len();
+        // Optimization: use sized caller when possible
+        if arg_len < SIZED_CALLERS.len() {
+            return SIZED_CALLERS[arg_len](self, result, args);
+        }
+
+        // Get `rvalue: *mut c_void` result pointer
+        let result_pointer = if self.result_info.size == 0 {
+            VOID_RESULT_PTR
+        } else {
+            result.get_ffi_data()?.get_inner_pointer()
+        }
+        .cast::<c_void>();
+
+        // Create `avalue: *mut *mut c_void` argument list
+        let mut arg_list = Vec::<*mut c_void>::with_capacity(arg_len);
+        for index in 0..arg_len {
+            let arg_value = args
+                .get(index)
+                .ok_or_else(|| LuaError::external(format!("Argument {index} required")))?
+                .as_userdata()
+                .ok_or_else(|| LuaError::external("Argument must be a RefData"))?;
+
+            if let Ok(arg_ref) = arg_value.borrow::<RefData>() {
+                arg_list.push(arg_ref.get_inner_pointer().cast::<c_void>());
+            } else {
+                return Err(LuaError::external("Argument must be a RefData"));
+            }
+        }
+
+        // Call libffi::raw::ffi_call
+        ffi_call(
+            self.cif,
+            Some(*self.code.as_safe_fun()),
+            result_pointer,
+            arg_list.as_mut_ptr(),
+        );
+
+        Ok(())
+    }
+}
+
+impl LuaUserData for CallableData {
+    fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
+        methods.add_meta_method(
+            LuaMetaMethod::Call,
+            |_lua, this: &CallableData, mut args: LuaMultiValue| {
+                let result = args.pop_front().ok_or_else(|| {
+                    LuaError::external("First argument 'result' must be a RefData, BoxData or nil")
+                })?;
+                unsafe { this.call(result, args) }
+            },
+        );
+        methods.add_meta_method(LuaMetaMethod::ToString, |_lua, this, ()| {
+            Ok(this.stringify())
+        });
+    }
+}
diff --git a/crates/lune-std-ffi/src/data/closure_data.rs b/crates/lune-std-ffi/src/data/closure_data.rs
new file mode 100644
index 0000000..93d21b4
--- /dev/null
+++ b/crates/lune-std-ffi/src/data/closure_data.rs
@@ -0,0 +1,161 @@
+use core::ffi::c_void;
+use std::{borrow::Borrow, ptr};
+
+use libffi::{
+    low::{closure_alloc, closure_free, ffi_cif},
+    raw::{ffi_closure, ffi_prep_closure_loc},
+};
+use mlua::prelude::*;
+
+use super::{
+    association_names::REF_INNER,
+    ref_data::{RefBounds, RefData, RefFlag, UNSIZED_BOUNDS},
+};
+use crate::ffi::{
+    association,
+    libffi_helper::{ffi_status_assert, SIZE_OF_POINTER},
+    FfiArg, FfiData, FfiResult,
+};
+
+// A closure that can be created with lua function.
+pub struct ClosureData {
+    lua: *const Lua,
+    closure: *mut ffi_closure,
+    code: Box<*mut c_void>,
+    arg_info_list: Vec<FfiArg>,
+    result_info: FfiResult,
+    func: LuaRegistryKey,
+}
+
+impl Drop for ClosureData {
+    fn drop(&mut self) {
+        unsafe {
+            closure_free(self.closure);
+        }
+    }
+}
+
+const RESULT_REF_FLAGS: u8 = RefFlag::Writable.value() | RefFlag::Offsetable.value();
+const CLOSURE_REF_FLAGS: u8 = RefFlag::Function.value();
+
+// Process C -> Lua function call
+unsafe extern "C" fn callback(
+    cif: *mut ffi_cif,
+    result_pointer: *mut c_void,
+    arg_pointers: *mut *mut c_void,
+    closure_data: *mut c_void,
+) {
+    let closure_data = closure_data.cast::<ClosureData>().as_ref().unwrap();
+    let lua = closure_data.lua.as_ref().unwrap();
+    let len = (*cif).nargs as usize;
+    let mut args = Vec::<LuaValue>::with_capacity(len + 1);
+
+    // Push result pointer (ref)
+    args.push(LuaValue::UserData(
+        lua.create_userdata(RefData::new(
+            result_pointer.cast::<()>(),
+            RESULT_REF_FLAGS,
+            RefBounds::new(0, closure_data.result_info.size),
+        ))
+        .unwrap(),
+    ));
+
+    // Push arg pointer (ref)
+    for i in 0..len {
+        let arg_info = closure_data.arg_info_list.get(i).unwrap();
+        args.push(LuaValue::UserData(
+            lua.create_userdata(RefData::new(
+                (*arg_pointers.add(i)).cast::<()>(),
+                arg_info.callback_ref_flag,
+                RefBounds::new(0, arg_info.size),
+            ))
+            .unwrap(),
+        ));
+    }
+
+    closure_data
+        .func
+        .borrow()
+        .into_lua(lua)
+        .unwrap()
+        .as_function()
+        .unwrap()
+        .call::<_, ()>(LuaMultiValue::from_vec(args))
+        .unwrap();
+}
+
+impl ClosureData {
+    // Allocate new ffi closure with lua function
+    pub fn alloc(
+        lua: &Lua,
+        cif: *mut ffi_cif,
+        arg_info_list: Vec<FfiArg>,
+        result_info: FfiResult,
+        func: LuaRegistryKey,
+    ) -> LuaResult<LuaAnyUserData> {
+        let (closure, code) = closure_alloc();
+        let code = code.as_mut_ptr();
+
+        let closure_data = lua.create_userdata(ClosureData {
+            lua: ptr::from_ref(lua),
+            closure,
+            code: Box::new(code),
+            arg_info_list,
+            result_info,
+            func,
+        })?;
+
+        let closure_data_ptr = ptr::from_ref(&*closure_data.borrow::<ClosureData>()?);
+
+        ffi_status_assert(unsafe {
+            ffi_prep_closure_loc(
+                closure,
+                cif,
+                Some(callback),
+                closure_data_ptr.cast::<c_void>().cast_mut(),
+                code,
+            )
+        })?;
+
+        Ok(closure_data)
+    }
+
+    // Stringify for pretty-print, with hex format address
+    pub fn stringify(&self) -> String {
+        format!("0x{:x}", unsafe { self.get_inner_pointer() } as usize)
+    }
+}
+
+impl FfiData for ClosureData {
+    unsafe fn get_inner_pointer(&self) -> *mut () {
+        ptr::from_ref::<*mut c_void>(&*self.code)
+            .cast::<()>()
+            .cast_mut()
+    }
+    fn check_inner_boundary(&self, offset: isize, size: usize) -> bool {
+        (offset as usize) + size <= SIZE_OF_POINTER
+    }
+    fn is_readable(&self) -> bool {
+        false
+    }
+    fn is_writable(&self) -> bool {
+        false
+    }
+}
+
+impl LuaUserData for ClosureData {
+    fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
+        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,
+            ))?;
+            association::set(lua, REF_INNER, &ref_data, &this)?;
+            Ok(ref_data)
+        });
+        methods.add_meta_method(LuaMetaMethod::ToString, |_lua, this, ()| {
+            Ok(this.stringify())
+        });
+    }
+}
diff --git a/crates/lune-std-ffi/src/data/helper.rs b/crates/lune-std-ffi/src/data/helper.rs
new file mode 100644
index 0000000..0b7de25
--- /dev/null
+++ b/crates/lune-std-ffi/src/data/helper.rs
@@ -0,0 +1,103 @@
+use mlua::prelude::*;
+
+use super::{FfiData, GetFfiData};
+
+pub mod method_provider {
+    use super::*;
+
+    // Implement copyFrom method
+    pub fn provide_copy_from<'lua, Target, M>(methods: &mut M)
+    where
+        Target: FfiData + 'static,
+        M: LuaUserDataMethods<'lua, Target>,
+    {
+        methods.add_function(
+            "copyFrom",
+            |_lua,
+             (this_userdata, src, length, dst_offset, src_offset): (
+                LuaAnyUserData,
+                LuaAnyUserData,
+                usize,
+                Option<isize>,
+                Option<isize>,
+            )| unsafe {
+                let this = this_userdata.borrow::<Target>()?;
+                let dst_offset = dst_offset.unwrap_or(0);
+                let src_offset = src_offset.unwrap_or(0);
+                let src = src.get_ffi_data()?;
+
+                if !src.check_inner_boundary(src_offset, length) {
+                    return Err(LuaError::external("Source out of bounds"));
+                }
+                if !this.check_inner_boundary(dst_offset, length) {
+                    return Err(LuaError::external("Self out of bounds"));
+                }
+
+                this.copy_from(&src, length, dst_offset, src_offset);
+
+                Ok(this_userdata.clone())
+            },
+        );
+    }
+
+    // Implement readString method
+    pub fn provide_read_string<'lua, Target, M>(methods: &mut M)
+    where
+        Target: FfiData + 'static,
+        M: LuaUserDataMethods<'lua, Target>,
+    {
+        methods.add_method(
+            "readString",
+            |lua, this, (length, offset): (usize, Option<isize>)| unsafe {
+                let offset = offset.unwrap_or(0);
+
+                if !this.check_inner_boundary(offset, length) {
+                    return Err(LuaError::external("Source out of bounds"));
+                }
+
+                lua.create_string(this.read_string(length, offset))
+            },
+        );
+    }
+
+    // Implement writeString method
+    pub fn provide_write_string<'lua, Target, M>(methods: &mut M)
+    where
+        Target: FfiData + 'static,
+        M: LuaUserDataMethods<'lua, Target>,
+    {
+        methods.add_function(
+            "writeString",
+            |_lua,
+             (this_userdata, string, length, dst_offset, src_offset): (
+                LuaAnyUserData,
+                LuaString,
+                Option<usize>,
+                Option<isize>,
+                Option<usize>,
+            )| unsafe {
+                let string_len = string.as_bytes().len();
+                let dst_offset = dst_offset.unwrap_or(0);
+                let src_offset = src_offset.unwrap_or(0);
+                let length = length.unwrap_or_else(|| string_len - src_offset);
+                let this = this_userdata.borrow::<Target>()?;
+
+                // Source string boundary check
+                if string_len < src_offset + length {
+                    return Err(LuaError::external("Source out of bounds"));
+                }
+
+                // Self boundary check
+                if !this.check_inner_boundary(dst_offset, length) {
+                    return Err(LuaError::external("Self out of bounds"));
+                }
+
+                this.write_string(string, length, dst_offset, src_offset);
+                Ok(this_userdata.clone())
+            },
+        );
+    }
+
+    // TODO: Bit operation support
+    // TODO: writeBase64 and readBase64 methods
+}
diff --git a/crates/lune-std-ffi/src/data/lib_data.rs b/crates/lune-std-ffi/src/data/lib_data.rs
new file mode 100644
index 0000000..b8819f9
--- /dev/null
+++ b/crates/lune-std-ffi/src/data/lib_data.rs
@@ -0,0 +1,68 @@
+use dlopen2::raw::Library;
+use mlua::prelude::*;
+
+use super::{
+    association_names::SYM_INNER,
+    ref_data::{RefData, RefFlag, UNSIZED_BOUNDS},
+};
+use crate::ffi::association;
+
+const LIB_REF_FLAGS: u8 = RefFlag::Offsetable.value()
+    | RefFlag::Readable.value()
+    | RefFlag::Dereferenceable.value()
+    | RefFlag::Function.value();
+
+// Runtime dynamic loaded libraries
+pub struct LibData {
+    name: String,
+    lib: Library,
+}
+
+impl LibData {
+    // Open library then return library handle
+    pub fn new(libname: String) -> LuaResult<Self> {
+        match Library::open(&libname) {
+            Ok(t) => Ok(Self {
+                lib: t,
+                name: libname.clone(),
+            }),
+            Err(err) => Err(err.into_lua_err()),
+        }
+    }
+
+    // Get named symbol from library
+    pub fn find_symbol<'lua>(
+        lua: &'lua Lua,
+        this: LuaAnyUserData<'lua>,
+        name: String,
+    ) -> LuaResult<LuaAnyUserData<'lua>> {
+        let lib = this.borrow::<LibData>()?;
+        let sym = unsafe {
+            lib.lib
+                .symbol::<*const ()>(name.as_str())
+                .map_err(LuaError::external)?
+        };
+        let ffi_ref =
+            lua.create_userdata(RefData::new(sym.cast_mut(), LIB_REF_FLAGS, UNSIZED_BOUNDS))?;
+
+        // Library handle should live longer than retrieved symbol
+        association::set(lua, SYM_INNER, &ffi_ref, &this)?;
+
+        Ok(ffi_ref)
+    }
+
+    pub fn stringify(&self) -> String {
+        self.name.clone()
+    }
+}
+
+impl LuaUserData for LibData {
+    fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
+        methods.add_function("find", |lua, (this, name): (LuaAnyUserData, String)| {
+            LibData::find_symbol(lua, this, name)
+        });
+        methods.add_meta_method(LuaMetaMethod::ToString, |_lua, this, ()| {
+            Ok(this.stringify())
+        });
+    }
+}
diff --git a/crates/lune-std-ffi/src/data/mod.rs b/crates/lune-std-ffi/src/data/mod.rs
new file mode 100644
index 0000000..8294f21
--- /dev/null
+++ b/crates/lune-std-ffi/src/data/mod.rs
@@ -0,0 +1,68 @@
+use std::cell::Ref;
+
+use lune_utils::fmt::{pretty_format_value, ValueFormatConfig};
+use mlua::prelude::*;
+
+mod box_data;
+mod callable_data;
+mod closure_data;
+mod helper;
+mod lib_data;
+mod ref_data;
+
+pub use self::{
+    box_data::BoxData,
+    callable_data::CallableData,
+    closure_data::ClosureData,
+    lib_data::LibData,
+    ref_data::{create_nullref, RefBounds, RefData, RefFlag, UNSIZED_BOUNDS},
+};
+use crate::ffi::FfiData;
+
+// Named registry keys
+mod association_names {
+    pub const REF_INNER: &str = "__ref_inner";
+    pub const SYM_INNER: &str = "__syn_inner";
+}
+
+// Get dynamic FfiData handle from LuaValue and LuaAnyUserData
+pub trait GetFfiData {
+    fn get_ffi_data(&self) -> LuaResult<Ref<dyn FfiData>>;
+    fn is_ffi_data(&self) -> bool;
+}
+impl GetFfiData for LuaAnyUserData<'_> {
+    fn get_ffi_data(&self) -> LuaResult<Ref<dyn FfiData>> {
+        if self.is::<BoxData>() {
+            Ok(self.borrow::<BoxData>()? as Ref<dyn FfiData>)
+        } else if self.is::<RefData>() {
+            Ok(self.borrow::<RefData>()? as Ref<dyn FfiData>)
+        } else if self.is::<ClosureData>() {
+            Ok(self.borrow::<ClosureData>()? as Ref<dyn FfiData>)
+        } else {
+            let config = ValueFormatConfig::new();
+            Err(LuaError::external(format!(
+                "Expected FfiBox, FfiRef or ClosureData. got {}",
+                pretty_format_value(&LuaValue::UserData(self.to_owned()), &config)
+            )))
+        }
+    }
+    fn is_ffi_data(&self) -> bool {
+        self.is::<BoxData>() | self.is::<RefData>() | self.is::<ClosureData>()
+    }
+}
+impl GetFfiData for LuaValue<'_> {
+    fn get_ffi_data(&self) -> LuaResult<Ref<dyn FfiData>> {
+        self.as_userdata()
+            .ok_or_else(|| {
+                let config = ValueFormatConfig::new();
+                LuaError::external(format!(
+                    "Expected FfiBox, FfiRef or ClosureData. got {}",
+                    pretty_format_value(self, &config)
+                ))
+            })?
+            .get_ffi_data()
+    }
+    fn is_ffi_data(&self) -> bool {
+        self.as_userdata().map_or(false, GetFfiData::is_ffi_data)
+    }
+}
diff --git a/crates/lune-std-ffi/src/data/ref_data/bounds.rs b/crates/lune-std-ffi/src/data/ref_data/bounds.rs
new file mode 100644
index 0000000..4bdec8b
--- /dev/null
+++ b/crates/lune-std-ffi/src/data/ref_data/bounds.rs
@@ -0,0 +1,99 @@
+// Memory boundaries
+pub struct RefBounds {
+    // How much bytes available above
+    pub(crate) above: usize,
+    // How much bytes available below
+    pub(crate) below: usize,
+}
+
+pub const UNSIZED_BOUNDS: RefBounds = RefBounds {
+    above: usize::MAX,
+    below: usize::MAX,
+};
+
+impl RefBounds {
+    pub fn new(above: usize, below: usize) -> Self {
+        Self { above, below }
+    }
+
+    // Check offset is in boundary
+    #[inline]
+    pub fn check_offset(&self, offset: isize) -> bool {
+        let offset_abs = offset.unsigned_abs();
+        match offset.signum() {
+            -1 => self.above >= offset_abs,
+            1 => self.below >= offset_abs,
+            0 => true,
+            _ => unreachable!(),
+        }
+    }
+
+    // Check boundary with specific size
+    //
+    // -4 ∧ ────── Above = 4
+    // -3 │                      (Case1)
+    // -2 │  ┌──── Offset = -2 : offset >= 0 || abs(offset) <= above
+    // -1 │  │
+    //  0 │  │ Size = 4
+    //  1 │  │                   (Case2)
+    //  2 │  ∨ ─── End    = 2  : end = offset + size;
+    //  3 │                      end <= 0 || end <= below
+    //  4 ∨ ────── Below = 4
+    //
+    #[inline]
+    pub fn check_sized(&self, offset: isize, size: usize) -> bool {
+        // (Case1) offset over above
+        if offset < 0 && self.above < offset.unsigned_abs() {
+            return false;
+        }
+
+        // (Case2) end over below
+        let end = offset + (size as isize);
+        end <= 0 || self.below >= end.unsigned_abs()
+    }
+
+    // Calculate new boundaries with offset
+    // No boundary checking in here
+    //
+    // Above = 3
+    //   ∧ ───∧───
+    // -3│    │ New above = 2
+    // -2│    │
+    // -1│ <────── Offset = -1
+    //  0│    │
+    //  1│    │
+    //  2│    │
+    //  3│    │ New below = 4
+    //   ∨ ───∨───
+    // Below = 3
+    //
+    #[inline]
+    pub fn offset(&self, offset: isize) -> Self {
+        let sign = offset.signum();
+        let offset_abs = offset.unsigned_abs();
+
+        Self {
+            above: match sign {
+                -1 => self.above - offset_abs,
+                1 => self.above + offset_abs,
+                0 => self.above,
+                _ => unreachable!(),
+            },
+            below: match sign {
+                -1 => self.below + offset_abs,
+                1 => self.below - offset_abs,
+                0 => self.below,
+                _ => unreachable!(),
+            },
+        }
+    }
+}
+
+impl Clone for RefBounds {
+    fn clone(&self) -> Self {
+        Self {
+            above: self.above,
+            below: self.below,
+        }
+    }
+}
diff --git a/crates/lune-std-ffi/src/data/ref_data/flag.rs b/crates/lune-std-ffi/src/data/ref_data/flag.rs
new file mode 100644
index 0000000..a7068ad
--- /dev/null
+++ b/crates/lune-std-ffi/src/data/ref_data/flag.rs
@@ -0,0 +1,23 @@
+use crate::ffi::bit_field::*;
+
+pub enum RefFlag {
+    Leaked,
+    Dereferenceable,
+    Readable,
+    Writable,
+    Offsetable,
+    Function,
+}
+
+impl RefFlag {
+    pub const fn value(&self) -> u8 {
+        match self {
+            Self::Leaked => U8_MASK1,
+            Self::Dereferenceable => U8_MASK2,
+            Self::Writable => U8_MASK3,
+            Self::Readable => U8_MASK4,
+            Self::Offsetable => U8_MASK5,
+            Self::Function => U8_MASK6,
+        }
+    }
+}
diff --git a/crates/lune-std-ffi/src/data/ref_data/mod.rs b/crates/lune-std-ffi/src/data/ref_data/mod.rs
new file mode 100644
index 0000000..2f26bdd
--- /dev/null
+++ b/crates/lune-std-ffi/src/data/ref_data/mod.rs
@@ -0,0 +1,204 @@
+use std::{mem::ManuallyDrop, ptr};
+
+use mlua::prelude::*;
+
+use super::helper::method_provider;
+use crate::{
+    data::association_names::REF_INNER,
+    ffi::{association, bit_field::*, FfiData},
+};
+
+mod bounds;
+mod flag;
+
+pub use self::{
+    bounds::{RefBounds, UNSIZED_BOUNDS},
+    flag::RefFlag,
+};
+
+// Box:ref():ref() should not be able to modify, Only for external
+const REF_REF_FLAGS: u8 = 0;
+
+const DEREF_REF_FLAG: u8 = RefFlag::Dereferenceable.value()
+    | RefFlag::Function.value()
+    | RefFlag::Offsetable.value()
+    | RefFlag::Readable.value()
+    | RefFlag::Writable.value();
+
+// A referenced memory address box. Possible to read and write through types.
+pub struct RefData {
+    ptr: ManuallyDrop<Box<*mut ()>>,
+    pub(crate) flags: u8,
+    boundary: RefBounds,
+}
+
+impl RefData {
+    pub fn new(ptr: *mut (), flags: u8, boundary: RefBounds) -> Self {
+        Self {
+            ptr: ManuallyDrop::new(Box::new(ptr)),
+            flags,
+            boundary,
+        }
+    }
+
+    pub fn update<'lua>(
+        lua: &'lua Lua,
+        this: LuaAnyUserData<'lua>,
+        ptr: *mut (),
+        flags: u8,
+        boundary: RefBounds,
+    ) -> LuaResult<()> {
+        let mut target = this.borrow_mut::<RefData>()?;
+        association::set(lua, REF_INNER, &this, LuaNil)?;
+
+        **target.ptr = ptr;
+        target.flags = flags;
+        target.boundary = boundary;
+
+        Ok(())
+    }
+
+    // Create reference of this reference
+    pub fn luaref<'lua>(
+        lua: &'lua Lua,
+        this: LuaAnyUserData<'lua>,
+    ) -> LuaResult<LuaAnyUserData<'lua>> {
+        let target = this.borrow::<RefData>()?;
+
+        let luaref = lua.create_userdata(RefData::new(
+            ptr::from_ref(&**target.ptr) as *mut (),
+            REF_REF_FLAGS,
+            RefBounds {
+                below: 0,
+                above: size_of::<usize>(),
+            },
+        ))?;
+
+        // Make sure new reference live longer then this reference
+        association::set(lua, REF_INNER, &luaref, &this)?;
+
+        Ok(luaref)
+    }
+
+    // Dereference this reference
+    pub unsafe fn dereference(&self) -> LuaResult<Self> {
+        // Check dereferenceable
+        if !u8_test(self.flags, RefFlag::Dereferenceable.value()) {
+            return Err(LuaError::external("Reference is not dereferenceable"));
+        }
+
+        // Check boundary
+        if !self.boundary.check_sized(0, size_of::<usize>()) {
+            return Err(LuaError::external("Out of bounds"));
+        }
+
+        Ok(Self::new(
+            *self.ptr.cast::<*mut ()>(),
+            DEREF_REF_FLAG,
+            UNSIZED_BOUNDS,
+        ))
+    }
+
+    pub fn is_null(&self) -> bool {
+        // * ManuallyDrop wrapper
+        // * Box wrapper
+        (**self.ptr) as usize == 0
+    }
+
+    pub fn leak(&mut self) {
+        self.flags = u8_set(self.flags, RefFlag::Leaked.value(), true);
+    }
+
+    // Create new reference with specific offset from this reference
+    pub unsafe fn offset(&self, offset: isize) -> LuaResult<Self> {
+        // Check offsetable
+        if u8_test_not(self.flags, RefFlag::Offsetable.value()) {
+            return Err(LuaError::external("Reference is not offsetable"));
+        }
+
+        // Check boundary
+        if !self.boundary.check_offset(offset) {
+            return Err(LuaError::external(format!(
+                "Offset out of bounds (high: {}, low: {}, got {})",
+                self.boundary.above, self.boundary.below, offset
+            )));
+        }
+
+        let boundary = self.boundary.offset(offset);
+        Ok(Self::new(
+            self.ptr.byte_offset(offset),
+            u8_set(self.flags, RefFlag::Leaked.value(), false),
+            boundary,
+        ))
+    }
+
+    // Stringify for pretty-print, with hex format address
+    pub fn stringify(&self) -> String {
+        format!("0x{:x}", **self.ptr as usize)
+    }
+}
+
+impl Drop for RefData {
+    fn drop(&mut self) {
+        if u8_test_not(self.flags, RefFlag::Leaked.value()) {
+            unsafe { ManuallyDrop::drop(&mut self.ptr) };
+        }
+    }
+}
+
+impl FfiData for RefData {
+    fn check_inner_boundary(&self, offset: isize, size: usize) -> bool {
+        self.boundary.check_sized(offset, size)
+    }
+    #[inline]
+    unsafe fn get_inner_pointer(&self) -> *mut () {
+        **self.ptr
+    }
+    fn is_readable(&self) -> bool {
+        u8_test(self.flags, RefFlag::Readable.value())
+    }
+    fn is_writable(&self) -> bool {
+        u8_test(self.flags, RefFlag::Writable.value())
+    }
+}
+
+impl LuaUserData for RefData {
+    fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
+        method_provider::provide_copy_from(methods);
+        method_provider::provide_read_string(methods);
+        method_provider::provide_write_string(methods);
+
+        methods.add_method("deref", |_lua, this, ()| unsafe { this.dereference() });
+        methods.add_function("offset", |lua, (this, offset): (LuaAnyUserData, isize)| {
+            let ffiref = unsafe { this.borrow::<RefData>()?.offset(offset)? };
+            let userdata = lua.create_userdata(ffiref)?;
+
+            // If the ref holds a box or reference, make sure the new ref also holds it
+            if let Some(t) = association::get(lua, REF_INNER, &this)? {
+                association::set(lua, REF_INNER, &userdata, t)?;
+            }
+
+            Ok(userdata)
+        });
+        methods.add_function_mut("leak", |lua, this: LuaAnyUserData| {
+            this.borrow_mut::<RefData>()?.leak();
+            RefData::luaref(lua, this)
+        });
+        methods.add_function("ref", |lua, this: LuaAnyUserData| {
+            RefData::luaref(lua, this)
+        });
+        methods.add_method("isNull", |_lua, this, ()| Ok(this.is_null()));
+        methods.add_meta_method(LuaMetaMethod::ToString, |_lua, this, ()| {
+            Ok(this.stringify())
+        });
+    }
+}
+
+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
+        UNSIZED_BOUNDS,
+    ))
+}
diff --git a/crates/lune-std-ffi/src/ffi/association.rs b/crates/lune-std-ffi/src/ffi/association.rs
new file mode 100644
index 0000000..f9a68c8
--- /dev/null
+++ b/crates/lune-std-ffi/src/ffi/association.rs
@@ -0,0 +1,52 @@
+use mlua::prelude::*;
+
+// This is a small library that helps you set the dependencies of data in Lua.
+// In FFI, there is often data that is dependent on other data.
+// However, if you use user_value to inform Lua of the dependency,
+// a table will be created for each userdata.
+// To prevent this, we place a weak reference table in the named registry
+// and simulate what mlua does.
+
+// If the dependency is deep, the value may be completely destroyed when
+// gc is performed multiple times. To prevent this situation, FFI should copy
+// dependency if possible.
+
+// You can delete the relationship by changing 'associated' to nil
+#[inline]
+pub fn set<'lua, T, U>(lua: &'lua Lua, regname: &str, value: T, associated: U) -> LuaResult<()>
+where
+    T: IntoLua<'lua>,
+    U: IntoLua<'lua>,
+{
+    let table = match lua.named_registry_value::<LuaValue>(regname)? {
+        LuaValue::Nil => {
+            let table = lua.create_table()?;
+            lua.set_named_registry_value(regname, table.clone())?;
+            let meta = lua.create_table()?;
+            meta.set("__mode", "k")?;
+            table.set_metatable(Some(meta));
+            table
+        }
+        LuaValue::Table(t) => t,
+        _ => panic!(""),
+    };
+
+    table.set(value, associated)?;
+
+    Ok(())
+}
+
+// Returns the Lua value that 'value' keeps.
+// If there is no table in registry, it returns None.
+// If there is no value in table, it returns LuaNil.
+#[inline]
+pub fn get<'lua, T>(lua: &'lua Lua, regname: &str, value: T) -> LuaResult<Option<LuaValue<'lua>>>
+where
+    T: IntoLua<'lua>,
+{
+    match lua.named_registry_value::<LuaValue>(regname)? {
+        LuaValue::Nil => Ok(None),
+        LuaValue::Table(t) => Ok(Some(t.get(value)?)),
+        _ => panic!(),
+    }
+}
diff --git a/crates/lune-std-ffi/src/ffi/bit_field.rs b/crates/lune-std-ffi/src/ffi/bit_field.rs
new file mode 100644
index 0000000..175d588
--- /dev/null
+++ b/crates/lune-std-ffi/src/ffi/bit_field.rs
@@ -0,0 +1,31 @@
+#![allow(unused)]
+
+// Simple bit field library for handling data flags
+
+pub const U8_MASK1: u8 = 1;
+pub const U8_MASK2: u8 = 2;
+pub const U8_MASK3: u8 = 4;
+pub const U8_MASK4: u8 = 8;
+pub const U8_MASK5: u8 = 16;
+pub const U8_MASK6: u8 = 32;
+pub const U8_MASK7: u8 = 64;
+pub const U8_MASK8: u8 = 128;
+
+#[inline]
+pub fn u8_test(bits: u8, mask: u8) -> bool {
+    bits & mask != 0
+}
+
+#[inline]
+pub fn u8_test_not(bits: u8, mask: u8) -> bool {
+    bits & mask == 0
+}
+
+#[inline]
+pub fn u8_set(bits: u8, mask: u8, val: bool) -> u8 {
+    if val {
+        bits | mask
+    } else {
+        bits & !mask
+    }
+}
diff --git a/crates/lune-std-ffi/src/ffi/cast.rs b/crates/lune-std-ffi/src/ffi/cast.rs
new file mode 100644
index 0000000..bbbe580
--- /dev/null
+++ b/crates/lune-std-ffi/src/ffi/cast.rs
@@ -0,0 +1,36 @@
+use std::cell::Ref;
+
+use mlua::prelude::*;
+use num::cast::AsPrimitive;
+
+use super::FfiData;
+
+// Cast number type to another number type, with num::cast library
+#[inline]
+pub fn num_cast<From, Into>(
+    from: &Ref<dyn FfiData>,
+    into: &Ref<dyn FfiData>,
+    from_offset: isize,
+    into_offset: isize,
+) -> LuaResult<()>
+where
+    From: AsPrimitive<Into>,
+    Into: 'static + Copy,
+{
+    let from_ptr = unsafe {
+        from.get_inner_pointer()
+            .byte_offset(from_offset)
+            .cast::<From>()
+    };
+    let into_ptr = unsafe {
+        into.get_inner_pointer()
+            .byte_offset(into_offset)
+            .cast::<Into>()
+    };
+
+    unsafe {
+        *into_ptr = (*from_ptr).as_();
+    }
+
+    Ok(())
+}
diff --git a/crates/lune-std-ffi/src/ffi/libffi_helper.rs b/crates/lune-std-ffi/src/ffi/libffi_helper.rs
new file mode 100644
index 0000000..bfb73a5
--- /dev/null
+++ b/crates/lune-std-ffi/src/ffi/libffi_helper.rs
@@ -0,0 +1,43 @@
+use std::ptr::{self, null_mut};
+
+use libffi::{low, raw};
+use mlua::prelude::*;
+
+pub const SIZE_OF_POINTER: usize = size_of::<*mut ()>();
+
+// Get ensured size of ctype (raw::libffi_type)
+pub fn get_ensured_size(ffi_type: *mut raw::ffi_type) -> LuaResult<usize> {
+    let mut cif = low::ffi_cif::default();
+    let result = unsafe {
+        raw::ffi_prep_cif(
+            ptr::from_mut(&mut cif),
+            raw::ffi_abi_FFI_DEFAULT_ABI,
+            0,
+            ffi_type,
+            null_mut(),
+        )
+    };
+
+    ffi_status_assert(result)?;
+    unsafe { Ok((*ffi_type).size) }
+}
+
+// Converts ffi status into &str for formatting
+const FFI_STATUS_NAMES: [&str; 4] = [
+    "ffi_status_FFI_OK",
+    "ffi_status_FFI_BAD_TYPEDEF",
+    "ffi_status_FFI_BAD_ABI",
+    "ffi_status_FFI_BAD_ARGTYPE",
+];
+
+// Check ffi_result is OK
+pub fn ffi_status_assert(result: raw::ffi_status) -> LuaResult<()> {
+    if result == raw::ffi_status_FFI_OK {
+        Ok(())
+    } else {
+        Err(LuaError::external(format!(
+            "ffi_status assertion failed. expected result {}, got {}",
+            FFI_STATUS_NAMES[0], FFI_STATUS_NAMES[result as usize]
+        )))
+    }
+}
diff --git a/crates/lune-std-ffi/src/ffi/mod.rs b/crates/lune-std-ffi/src/ffi/mod.rs
new file mode 100644
index 0000000..36b27d9
--- /dev/null
+++ b/crates/lune-std-ffi/src/ffi/mod.rs
@@ -0,0 +1,129 @@
+use std::cell::Ref;
+
+use mlua::prelude::*;
+
+pub mod association;
+pub mod bit_field;
+mod cast;
+pub mod libffi_helper;
+
+pub use self::cast::num_cast;
+
+// Common type information
+pub trait FfiSize {
+    fn get_size(&self) -> usize;
+}
+pub trait FfiSignedness {
+    fn get_signedness(&self) -> bool {
+        false
+    }
+}
+
+// Provide conversion between luau value and ffi types
+pub trait FfiConvert {
+    // Write LuaValue into FfiData
+    unsafe fn value_into_data<'lua>(
+        &self,
+        lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+        value: LuaValue<'lua>,
+    ) -> LuaResult<()>;
+
+    // Read LuaValue from FfiData
+    unsafe fn value_from_data<'lua>(
+        &self,
+        lua: &'lua Lua,
+        offset: isize,
+        data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<LuaValue<'lua>>;
+
+    unsafe fn copy_data(
+        &self,
+        lua: &Lua,
+        dst_offset: isize,
+        src_offset: isize,
+        dst: &Ref<dyn FfiData>,
+        src: &Ref<dyn FfiData>,
+    ) -> LuaResult<()>;
+
+    unsafe fn stringify_data(
+        &self,
+        _lua: &Lua,
+        _offset: isize,
+        _data_handle: &Ref<dyn FfiData>,
+    ) -> LuaResult<String> {
+        Err(LuaError::external("Stringify method not implemented"))
+    }
+}
+
+// Provide read, write, boundary check methods for datas
+pub trait FfiData {
+    fn check_inner_boundary(&self, offset: isize, size: usize) -> bool;
+    unsafe fn get_inner_pointer(&self) -> *mut ();
+    fn is_writable(&self) -> bool;
+    fn is_readable(&self) -> bool;
+    unsafe fn copy_from(
+        &self,
+        src: &Ref<dyn FfiData>,
+        length: usize,
+        dst_offset: isize,
+        src_offset: isize,
+    ) {
+        self.get_inner_pointer()
+            .cast::<u8>()
+            .byte_offset(dst_offset)
+            .copy_from(
+                src.get_inner_pointer().cast::<u8>().byte_offset(src_offset),
+                length,
+            );
+    }
+    unsafe fn read_string(&self, length: usize, offset: isize) -> Vec<u8> {
+        let mut string = Vec::<u8>::with_capacity(length);
+        string.as_mut_ptr().copy_from(
+            self.get_inner_pointer().cast::<u8>().byte_offset(offset),
+            length,
+        );
+        string.set_len(length);
+        string
+    }
+    unsafe fn write_string(
+        &self,
+        src: LuaString,
+        length: usize,
+        dst_offset: isize,
+        src_offset: usize,
+    ) {
+        self.get_inner_pointer()
+            .cast::<u8>()
+            .byte_offset(dst_offset)
+            .copy_from(
+                src.as_bytes().as_ptr().cast::<u8>().byte_add(src_offset),
+                length,
+            );
+    }
+}
+
+// Function argument informations
+pub struct FfiArg {
+    pub size: usize,
+    pub callback_ref_flag: u8,
+}
+impl Clone for FfiArg {
+    fn clone(&self) -> Self {
+        Self {
+            size: self.size,
+            callback_ref_flag: self.callback_ref_flag,
+        }
+    }
+}
+
+// Function result information
+pub struct FfiResult {
+    pub size: usize,
+}
+impl Clone for FfiResult {
+    fn clone(&self) -> Self {
+        Self { size: self.size }
+    }
+}
diff --git a/crates/lune-std-ffi/src/lib.rs b/crates/lune-std-ffi/src/lib.rs
new file mode 100644
index 0000000..62b2572
--- /dev/null
+++ b/crates/lune-std-ffi/src/lib.rs
@@ -0,0 +1,39 @@
+#![allow(clippy::cargo_common_metadata)]
+
+use std::ffi::c_void;
+
+use libc::free;
+use lune_utils::TableBuilder;
+use mlua::prelude::*;
+
+mod c;
+mod data;
+mod ffi;
+
+use crate::{
+    c::{export_c, export_fixed_types},
+    data::{create_nullref, BoxData, GetFfiData, LibData},
+};
+
+/**
+    Creates the `ffi` standard library module.
+
+    # Errors
+
+    Errors when out of memory.
+*/
+pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
+    let result = TableBuilder::new(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("isInteger", |_lua, num: LuaValue| Ok(num.is_integer()))?
+        .with_function("free", |_lua, data: LuaAnyUserData| {
+            unsafe { free(data.get_ffi_data()?.get_inner_pointer().cast::<c_void>()) };
+            Ok(())
+        })?
+        .with_values(export_fixed_types(lua)?)?
+        .with_value("c", export_c(lua)?)?;
+
+    result.build_readonly()
+}
diff --git a/crates/lune-std/Cargo.toml b/crates/lune-std/Cargo.toml
index 07762b6..bfe61da 100644
--- a/crates/lune-std/Cargo.toml
+++ b/crates/lune-std/Cargo.toml
@@ -24,6 +24,7 @@ default = [
     "serde",
     "stdio",
     "task",
+    "ffi",
 ]
 
 datetime = ["dep:lune-std-datetime"]
@@ -36,6 +37,7 @@ roblox = ["dep:lune-std-roblox"]
 serde = ["dep:lune-std-serde"]
 stdio = ["dep:lune-std-stdio"]
 task = ["dep:lune-std-task"]
+ffi = ["dep:lune-std-ffi"]
 
 [dependencies]
 mlua = { version = "0.9.9", features = ["luau"] }
@@ -57,3 +59,4 @@ lune-std-roblox = { optional = true, version = "0.1.4", path = "../lune-std-robl
 lune-std-serde = { optional = true, version = "0.1.2", path = "../lune-std-serde" }
 lune-std-stdio = { optional = true, version = "0.1.2", path = "../lune-std-stdio" }
 lune-std-task = { optional = true, version = "0.1.2", path = "../lune-std-task" }
+lune-std-ffi = { optional = true, version = "0.1.1", path = "../lune-std-ffi" }
diff --git a/crates/lune-std/src/lib.rs b/crates/lune-std/src/lib.rs
index a29bef0..9808912 100644
--- a/crates/lune-std/src/lib.rs
+++ b/crates/lune-std/src/lib.rs
@@ -6,10 +6,12 @@ mod global;
 mod globals;
 mod library;
 mod luaurc;
+mod unsafe_library;
 
 pub use self::global::LuneStandardGlobal;
 pub use self::globals::version::set_global_version;
 pub use self::library::LuneStandardLibrary;
+pub use self::unsafe_library::{get_unsafe_library_enabled, set_unsafe_library_enabled};
 
 /**
     Injects all standard globals into the given Lua state / VM.
diff --git a/crates/lune-std/src/library.rs b/crates/lune-std/src/library.rs
index 9a301f5..80e2b5c 100644
--- a/crates/lune-std/src/library.rs
+++ b/crates/lune-std/src/library.rs
@@ -2,8 +2,10 @@ use std::str::FromStr;
 
 use mlua::prelude::*;
 
+use crate::get_unsafe_library_enabled;
+
 /**
-    A standard library provided by Lune.
+    A standard library probloxrovided by Lune.
 */
 #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
 #[rustfmt::skip]
@@ -18,6 +20,7 @@ pub enum LuneStandardLibrary {
     #[cfg(feature = "serde")]    Serde,
     #[cfg(feature = "stdio")]    Stdio,
     #[cfg(feature = "roblox")]   Roblox,
+    #[cfg(feature = "ffi")]      Ffi,
 }
 
 impl LuneStandardLibrary {
@@ -36,6 +39,7 @@ impl LuneStandardLibrary {
         #[cfg(feature = "serde")]    Self::Serde,
         #[cfg(feature = "stdio")]    Self::Stdio,
         #[cfg(feature = "roblox")]   Self::Roblox,
+        #[cfg(feature = "ffi")]      Self::Ffi,
     ];
 
     /**
@@ -56,6 +60,31 @@ impl LuneStandardLibrary {
             #[cfg(feature = "serde")]    Self::Serde    => "serde",
             #[cfg(feature = "stdio")]    Self::Stdio    => "stdio",
             #[cfg(feature = "roblox")]   Self::Roblox   => "roblox",
+            #[cfg(feature = "ffi")]      Self::Ffi      => "ffi",
+
+            _ => unreachable!("no standard library enabled"),
+        }
+    }
+
+    /**
+        Gets whether the library is unsafe.
+    */
+    #[must_use]
+    #[rustfmt::skip]
+    #[allow(unreachable_patterns)]
+    pub fn is_unsafe(&self) -> bool {
+        match self {
+            #[cfg(feature = "datetime")] Self::DateTime => false,
+            #[cfg(feature = "fs")]       Self::Fs       => false,
+            #[cfg(feature = "luau")]     Self::Luau     => false,
+            #[cfg(feature = "net")]      Self::Net      => false,
+            #[cfg(feature = "task")]     Self::Task     => false,
+            #[cfg(feature = "process")]  Self::Process  => false,
+            #[cfg(feature = "regex")]    Self::Regex    => false,
+            #[cfg(feature = "serde")]    Self::Serde    => false,
+            #[cfg(feature = "stdio")]    Self::Stdio    => false,
+            #[cfg(feature = "roblox")]   Self::Roblox   => false,
+            #[cfg(feature = "ffi")]      Self::Ffi      => true,
 
             _ => unreachable!("no standard library enabled"),
         }
@@ -66,11 +95,15 @@ impl LuneStandardLibrary {
 
         # Errors
 
-        If the library could not be created.
+        If the library could not be created, or if requiring an unsafe library without enabling the unsafe library.
     */
     #[rustfmt::skip]
     #[allow(unreachable_patterns)]
     pub fn module<'lua>(&self, lua: &'lua Lua) -> LuaResult<LuaMultiValue<'lua>> {
+        if self.is_unsafe() && !get_unsafe_library_enabled(lua) {
+            return Err(LuaError::external(format!("Standard library '{}' requires unsafe library enabled", self.name())));
+        }
+
         let res: LuaResult<LuaTable> = match self {
             #[cfg(feature = "datetime")] Self::DateTime => lune_std_datetime::module(lua),
             #[cfg(feature = "fs")]       Self::Fs       => lune_std_fs::module(lua),
@@ -82,6 +115,7 @@ impl LuneStandardLibrary {
             #[cfg(feature = "serde")]    Self::Serde    => lune_std_serde::module(lua),
             #[cfg(feature = "stdio")]    Self::Stdio    => lune_std_stdio::module(lua),
             #[cfg(feature = "roblox")]   Self::Roblox   => lune_std_roblox::module(lua),
+            #[cfg(feature = "ffi")]      Self::Ffi      => lune_std_ffi::module(lua),
 
             _ => unreachable!("no standard library enabled"),
         };
@@ -111,6 +145,7 @@ impl FromStr for LuneStandardLibrary {
             #[cfg(feature = "serde")]    "serde"    => Self::Serde,
             #[cfg(feature = "stdio")]    "stdio"    => Self::Stdio,
             #[cfg(feature = "roblox")]   "roblox"   => Self::Roblox,
+            #[cfg(feature = "ffi")]      "ffi"      => Self::Ffi,
 
             _ => {
                 return Err(format!(
diff --git a/crates/lune-std/src/unsafe_library.rs b/crates/lune-std/src/unsafe_library.rs
new file mode 100644
index 0000000..3166050
--- /dev/null
+++ b/crates/lune-std/src/unsafe_library.rs
@@ -0,0 +1,26 @@
+use mlua::prelude::*;
+
+struct UnsafeLibrary(bool);
+
+/**
+    Override unsafe library allowance
+*/
+pub fn set_unsafe_library_enabled(lua: &Lua, enabled: bool) {
+    lua.set_app_data(UnsafeLibrary(enabled));
+}
+
+/**
+    Returns whether unsafe libraries are allowed
+
+    # Panics
+
+    Panic if `UnsafeLib` app data doesn't exist.
+*/
+#[must_use]
+pub fn get_unsafe_library_enabled(lua: &Lua) -> bool {
+    if let Some(app_data) = lua.app_data_ref::<UnsafeLibrary>() {
+        app_data.0
+    } else {
+        false
+    }
+}
diff --git a/crates/lune/Cargo.toml b/crates/lune/Cargo.toml
index 426782f..a3df155 100644
--- a/crates/lune/Cargo.toml
+++ b/crates/lune/Cargo.toml
@@ -30,6 +30,7 @@ std-roblox = ["dep:lune-std", "lune-std/roblox", "dep:lune-roblox"]
 std-serde = ["dep:lune-std", "lune-std/serde"]
 std-stdio = ["dep:lune-std", "lune-std/stdio"]
 std-task = ["dep:lune-std", "lune-std/task"]
+std-ffi = ["dep:lune-std", "lune-std/ffi"]
 
 std = [
     "std-datetime",
@@ -42,6 +43,7 @@ std = [
     "std-serde",
     "std-stdio",
     "std-task",
+    "std-ffi",
 ]
 
 cli = ["dep:clap", "dep:include_dir", "dep:rustyline", "dep:zip_next"]
diff --git a/crates/lune/src/cli/repl.rs b/crates/lune/src/cli/repl.rs
index 78a6bd7..faeff73 100644
--- a/crates/lune/src/cli/repl.rs
+++ b/crates/lune/src/cli/repl.rs
@@ -17,7 +17,11 @@ enum PromptState {
 
 /// Launch an interactive REPL (default)
 #[derive(Debug, Clone, Default, Parser)]
-pub struct ReplCommand {}
+pub struct ReplCommand {
+    /// Allow unsafe libraries
+    #[clap(long, action)]
+    r#unsafe: bool,
+}
 
 impl ReplCommand {
     pub async fn run(self) -> Result<ExitCode> {
@@ -38,7 +42,7 @@ impl ReplCommand {
         let mut prompt_state = PromptState::Regular;
         let mut source_code = String::new();
 
-        let mut lune_instance = Runtime::new();
+        let mut lune_instance = Runtime::new().set_unsafe_library_enabled(self.r#unsafe);
 
         loop {
             let prompt = match prompt_state {
diff --git a/crates/lune/src/cli/run.rs b/crates/lune/src/cli/run.rs
index 6267ed7..8f198c4 100644
--- a/crates/lune/src/cli/run.rs
+++ b/crates/lune/src/cli/run.rs
@@ -14,6 +14,9 @@ use super::utils::files::{discover_script_path_including_lune_dirs, strip_sheban
 /// Run a script
 #[derive(Debug, Clone, Parser)]
 pub struct RunCommand {
+    /// Allow unsafe libraries
+    #[clap(long, action)]
+    r#unsafe: bool,
     /// Script name or full path to the file to run
     script_path: String,
     /// Arguments to pass to the script, stored in process.args
@@ -41,7 +44,9 @@ impl RunCommand {
         };
 
         // Create a new lune runtime with all globals & run the script
-        let mut rt = Runtime::new().with_args(self.script_args);
+        let mut rt = Runtime::new()
+            .with_args(self.script_args)
+            .set_unsafe_library_enabled(self.r#unsafe);
 
         let result = rt
             .run(&script_display_name, strip_shebang(script_contents))
diff --git a/crates/lune/src/rt/runtime.rs b/crates/lune/src/rt/runtime.rs
index 31e5b03..daeb0fd 100644
--- a/crates/lune/src/rt/runtime.rs
+++ b/crates/lune/src/rt/runtime.rs
@@ -54,6 +54,7 @@ impl RuntimeInner {
                 feature = "std-serde",
                 feature = "std-stdio",
                 feature = "std-task",
+                feature = "std-ffi",
             ))]
             {
                 lune_std::set_global_version(lua, env!("CARGO_PKG_VERSION"));
@@ -76,6 +77,7 @@ impl RuntimeInner {
                 feature = "std-serde",
                 feature = "std-stdio",
                 feature = "std-task",
+                feature = "std-ffi",
             ))]
             {
                 let g_table = lune_std::LuneStandardGlobal::GTable;
@@ -130,6 +132,15 @@ impl Runtime {
         self
     }
 
+    /**
+        Sets arguments to give in `process.args` for Lune scripts.
+    */
+    #[must_use]
+    pub fn set_unsafe_library_enabled(self, enabled: bool) -> Self {
+        lune_std::set_unsafe_library_enabled(self.inner.lua(), enabled);
+        self
+    }
+
     /**
         Runs a Lune script inside of the current runtime.
 
diff --git a/crates/lune/src/tests.rs b/crates/lune/src/tests.rs
index d5f5640..0a62219 100644
--- a/crates/lune/src/tests.rs
+++ b/crates/lune/src/tests.rs
@@ -31,7 +31,7 @@ macro_rules! create_tests {
             // The rest of the test logic can continue as normal
             let full_name = format!("{}/tests/{}.luau", workspace_dir.display(), $value);
             let script = read_to_string(&full_name).await?;
-            let mut lune = Runtime::new().with_args(
+            let mut lune = Runtime::new().set_unsafe_library_enabled(true).with_args(
                 ARGS
                     .clone()
                     .iter()
@@ -99,6 +99,34 @@ create_tests! {
     datetime_to_universal_time: "datetime/toUniversalTime",
 }
 
+#[cfg(feature = "std-ffi")]
+create_tests! {
+    ffi_external_closure_call_closure: "ffi/external_closure/callClosure",
+    ffi_external_closure_call_closure_with_pointer: "ffi/external_closure/callClosureWithPointer",
+    ffi_external_closure_call_hello_world: "ffi/external_closure/callHelloWorld",
+    ffi_external_math_add_int: "ffi/external_math/addInt",
+    ffi_external_math_mul_int: "ffi/external_math/mulInt",
+    ffi_external_pointer_pointer_read: "ffi/external_pointer/pointerRead",
+    ffi_external_pointer_pointer_write: "ffi/external_pointer/pointerWrite",
+    ffi_external_print_hello_world: "ffi/external_print/helloWorld",
+    ffi_external_struct_ab: "ffi/external_struct/ab",
+    ffi_pretty_print_arr: "ffi/pretty_print/arr",
+    ffi_pretty_print_box: "ffi/pretty_print/box",
+    ffi_pretty_print_fn: "ffi/pretty_print/fn",
+    ffi_pretty_print_ptr: "ffi/pretty_print/ptr",
+    ffi_pretty_print_struct: "ffi/pretty_print/struct",
+    ffi_pretty_print_type: "ffi/pretty_print/type",
+    ffi_types_arr: "ffi/types/arr",
+    ffi_types_ptr: "ffi/types/ptr",
+    ffi_types_struct: "ffi/types/struct",
+    ffi_cast: "ffi/cast",
+    ffi_free: "ffi/free",
+    ffi_is_integer: "ffi/isInteger",
+    ffi_read_boundary: "ffi/readBoundary",
+    ffi_read_write_string: "ffi/stringReadWrite",
+    ffi_write_boundary: "ffi/writeBoundary",
+}
+
 #[cfg(feature = "std-fs")]
 create_tests! {
     fs_files: "fs/files",
diff --git a/tests/ffi/README.md b/tests/ffi/README.md
new file mode 100644
index 0000000..3298b74
--- /dev/null
+++ b/tests/ffi/README.md
@@ -0,0 +1,113 @@
+<!-- markdownlint-disable MD036 -->
+<!-- markdownlint-disable MD033 -->
+
+# `tests/ffi`
+
+## Requirements
+
+gcc for library compiling (for external-\*)
+
+## Test Results
+
+**External tests**
+
+- [x] [external_closure](./external_closure/init.luau)
+- [x] [external_math](./external_math/init.luau)
+- [x] [external_pointer](./external_pointer/init.luau)
+- [x] [external_print](./external_print/init.luau)
+- [x] [external_struct](./external_struct/init.luau)
+
+**Luau-side**
+
+- [x] [cast](./cast.luau)
+- [x] [free](./free.luau)
+- [x] [isInteger](./isInteger.luau)
+- [x] [read_boundary](./read_boundary.luau)
+- [x] [write_boundary](./write_boundary.luau)
+
+**Types**
+
+- [x] [arr](./types/arr.luau)
+- [x] [ptr](./types/ptr.luau)
+- [x] [struct](./types/struct.luau)
+
+**Pretty Print**
+
+- [x] [arr](./pretty_print/arr.luau)
+- [ ] [box](./pretty_print/box.luau) Need assertion
+- [ ] [ref](./pretty_print/ref.luau) Need assertion
+- [ ] [lib](./pretty_print/lib.luau) Need assertion
+- [x] [fn](./pretty_print/fn.luau)
+- [x] [ptr](./pretty_print/ptr.luau)
+- [x] [struct](./pretty_print/struct.luau)
+- [x] [type](./pretty_print/type.luau)
+
+## Benchmark Results
+
+> Note: LuaJit's os.clock function returns process CPU time (used) which much smaller then Luau's os.clock output. In this benchmark, luau uses 'time.h' instead of os.clock. See [utility/proc_clock](./utility/proc_clock/init.luau)
+
+<details><summary><h3><a href="./benchmark/external_call/init.luau">benchmark/external_call</a></h3></summary>
+
+**Target external c function**
+
+```c
+int add(int a, int b) {
+    return a + b;
+}
+```
+
+bench_scale = 1000000
+
+**Lune ffi**
+
+Command: `cargo run run tests/ffi/benchmark/external_call`
+Command: `cargo run --profile=release run tests/ffi/benchmark/external_call`
+
+- Device1-Linux-PVE  
+  Lune release target: 0.205127 (sec)  
+  Lune dev target: 1.556489 (sec)
+
+  > Commit: ddf0c4c
+
+- Device2-Windows-11
+  Lune release target: 0.1875 (sec)  
+  Lune dev target: ? SEGFUALT (sec)
+
+  > Commit: ddf0c4c
+
+**C**
+
+- Device1-Linux-PVE: 0.001949 (sec)
+  > gcc (GCC) 14.2.1 20240910
+
+**LuaJit ffi**
+
+Command: `luajit tests/ffi/benchmark/external_call/luajit.lua`
+
+- Device1-Linux-PVE: 0.001682 (sec)
+  > LuaJIT 2.1.1727870382  
+  > (flags = JIT ON SSE3 SSE4.1 BMI2 fold cse dce fwd dse narrow loop abc sink fuse)
+
+**Deno ffi**
+
+Command: `deno run --unstable-ffi --allow-ffi ./tests/ffi/benchmark/external_call/deno.ts`
+
+- Device1-Linux-PVE: 0.006384 (sec)
+  > Deno 1.46.3 (v8 = 12.9.202.5-rusty)
+
+**Sysinformation**
+
+- Device1-Linux-PVE
+
+  > CPU: AMD Ryzen 5 7600 (12) @ 5.1  
+  > MEM: 61898MiB 5600 MT/s  
+  > KERNEL: 6.8.12-2-pve (Proxmox VE 8.2.7 x86_64)
+
+- Device2-Windows-11
+
+  > CPU: AMD Ryzen 5 7600 (4) @ 3.800GHz  
+  > MEM: 12250MiB 5600 MT/s  
+  > KERNEL: 10.0.22631 (Windows 11 x86_64)  
+  > HOST: QEMU Standard PC (Q35 + ICH9, 2009)
+
+</details>
diff --git a/tests/ffi/benchmark/external_call/deno.ts b/tests/ffi/benchmark/external_call/deno.ts
new file mode 100644
index 0000000..7332ae1
--- /dev/null
+++ b/tests/ffi/benchmark/external_call/deno.ts
@@ -0,0 +1,24 @@
+import { libSuffix } from "../../utils/libSuffix.ts";
+import { get_clock, get_offset } from "../../utils/proc_clock/deno.ts";
+
+const library_file = "./tests/ffi/benchmark/external_call/lib."+libSuffix;
+// @ts-ignore
+let library = Deno.dlopen(library_file, {
+    add: {
+        parameters: ["i32", "i32"],
+        result: "i32",
+    },
+});
+
+function bench_add(bench_size: number) {
+    let add = library.symbols.add;
+    let value = 0;
+    const before = get_clock();
+    for (let i=0; i<bench_size; i++) {
+        value = add(value,1);
+    }
+    const after = get_clock();
+    console.log(get_offset(before,after))
+}
+
+bench_add(1000000);
\ No newline at end of file
diff --git a/tests/ffi/benchmark/external_call/init.luau b/tests/ffi/benchmark/external_call/init.luau
new file mode 100644
index 0000000..14c6389
--- /dev/null
+++ b/tests/ffi/benchmark/external_call/init.luau
@@ -0,0 +1,31 @@
+local ffi = require("@lune/ffi")
+local lib = require("../../utils/compile")("./tests/ffi/benchmark/external_call/lib.c")
+local process = require("@lune/process")
+local c = ffi.c
+local BENCH_SCALE: number = tonumber(process.env.BENCH_SCALE) or 1000000
+
+-- Get clock provider
+local procClock = require("../../utils/proc_clock")
+local before, after = procClock.newBox()
+local getClock = procClock.getClock
+
+local add = c.fn({ c.int, c.int }, c.int):callable(lib:find("add"))
+
+local a = c.int:box(0)
+local delta = c.int:box(1)
+local a_ref = a:ref()
+local delta_ref = delta:ref()
+
+getClock(before)
+for i = 1, BENCH_SCALE do
+	add(a, a_ref, delta_ref)
+end
+getClock(after)
+
+print("lune-std-ffi: " .. procClock.getOffset(before, after))
+local result = c.int:readData(a)
+assert(result == BENCH_SCALE, `bench_add failed. result expected {BENCH_SCALE}, got {result}`)
+
+local cSideTime = ffi.box(ffi.f64.size)
+c.fn({}, ffi.f64):callable(lib:find("c_test"))(cSideTime)
+print("C level: " .. ffi.f64:readData(cSideTime))
diff --git a/tests/ffi/benchmark/external_call/lib.c b/tests/ffi/benchmark/external_call/lib.c
new file mode 100644
index 0000000..7d74114
--- /dev/null
+++ b/tests/ffi/benchmark/external_call/lib.c
@@ -0,0 +1,18 @@
+#include <time.h>
+
+int add(int a, int b) {
+    return a + b;
+}
+
+double c_test() {
+    clock_t before = clock();
+
+    int a = 0;
+    for (int i=0; i<1000000; i++) {
+        a = add(a, 1);
+    }
+
+    clock_t after = clock();
+
+    return (double)(after - before) / CLOCKS_PER_SEC;
+}
diff --git a/tests/ffi/benchmark/external_call/luajit.lua b/tests/ffi/benchmark/external_call/luajit.lua
new file mode 100644
index 0000000..b1eb6f4
--- /dev/null
+++ b/tests/ffi/benchmark/external_call/luajit.lua
@@ -0,0 +1,24 @@
+--!nolint
+--!nocheck
+
+local ffi = require("ffi")
+local BENCH_SCALE = 1000000
+
+ffi.cdef([[
+	int add(int a, int b);
+]])
+local lib = ffi.load("./tests/ffi/benchmark/external_call/lib.so")
+local add = lib.add
+local a = 0
+
+local before = os.clock()
+for i = 1, BENCH_SCALE do
+	a = add(a, 1)
+end
+local after = os.clock()
+
+print(after - before)
+assert(
+	a == BENCH_SCALE,
+	string.format("bench_add failed. result expected %d, got %d", BENCH_SCALE, a)
+)
diff --git a/tests/ffi/cast.luau b/tests/ffi/cast.luau
new file mode 100644
index 0000000..9c4d536
--- /dev/null
+++ b/tests/ffi/cast.luau
@@ -0,0 +1,13 @@
+local ffi = require("@lune/ffi")
+
+local floatBox = ffi.f32:box(1.2)
+local intBox = ffi.box(ffi.i32.size)
+
+ffi.f32:cast(ffi.i32, floatBox, intBox)
+
+local castedInt = ffi.i32:readData(intBox)
+
+assert(
+	castedInt == 1 and ffi.isInteger(castedInt),
+	"castedInt == 1 and ffi.isInteger(castedInt) assersion failed"
+)
diff --git a/tests/ffi/external_closure/callClosure.luau b/tests/ffi/external_closure/callClosure.luau
new file mode 100644
index 0000000..f6bd73c
--- /dev/null
+++ b/tests/ffi/external_closure/callClosure.luau
@@ -0,0 +1,13 @@
+local callableWrapper = require("../utils/callableWrapper")
+local ffi = require("@lune/ffi")
+local lib = require("../utils/compile")("./tests/ffi/external_closure/lib.c")
+local c = ffi.c
+
+-- Create closure
+local closure = c.fn({ c.int, c.int }, c.int):closure(function(ret, a, b)
+	c.int:writeData(ret, c.int:readData(a) + c.int:readData(b))
+end)
+
+local callClosure = callableWrapper(lib:find("call_closure"), { c.void:ptr() }, c.int)
+local result = callClosure(closure:ref())
+assert(result == 72, `callClosure failed. result expected 20000, got {result}`)
diff --git a/tests/ffi/external_closure/callClosureWithPointer.luau b/tests/ffi/external_closure/callClosureWithPointer.luau
new file mode 100644
index 0000000..2408464
--- /dev/null
+++ b/tests/ffi/external_closure/callClosureWithPointer.luau
@@ -0,0 +1,15 @@
+local callableWrapper = require("../utils/callableWrapper")
+local ffi = require("@lune/ffi")
+local lib = require("../utils/compile")("./tests/ffi/external_closure/lib.c")
+local c = ffi.c
+
+-- Create closure
+local closureWithPointer = c.fn({ c.int, c.int:ptr() }, c.int)
+	:closure(function(returnRef, aRef, bRef)
+		c.int:writeData(returnRef, c.int:readData(aRef) + c.int:readData(bRef:deref()))
+	end)
+
+local callClosureWithPointer =
+	callableWrapper(lib:find("call_closure_with_pointer"), { c.void:ptr() }, c.int)
+local result = callClosureWithPointer(closureWithPointer:ref())
+assert(result == 72, `closureWithPointer failed. result expected 20000, got {result}`)
diff --git a/tests/ffi/external_closure/callHelloWorld.luau b/tests/ffi/external_closure/callHelloWorld.luau
new file mode 100644
index 0000000..50a8b19
--- /dev/null
+++ b/tests/ffi/external_closure/callHelloWorld.luau
@@ -0,0 +1,11 @@
+local ffi = require("@lune/ffi")
+local lib = require("../utils/compile")("./tests/ffi/external_closure/lib.c")
+local c = ffi.c
+
+-- Create closure
+local helloWorld = c.fn({}, c.void):closure(function()
+	print("Hello world in lua closure!")
+end)
+
+local callHelloWorld = c.fn({ c.void:ptr() }, c.void):callable(lib:find("call_hello_world"))
+callHelloWorld(nil, helloWorld:ref())
diff --git a/tests/ffi/external_closure/lib.c b/tests/ffi/external_closure/lib.c
new file mode 100644
index 0000000..a5e7a77
--- /dev/null
+++ b/tests/ffi/external_closure/lib.c
@@ -0,0 +1,17 @@
+#include<stdio.h>
+
+typedef int (*lua_closure_t)(int, int);
+int call_closure(lua_closure_t lua_closure) {
+    return lua_closure(12, 24) * 2;
+}
+
+typedef void (*lua_hello_world_t)();
+void call_hello_world(lua_hello_world_t lua_closure) {
+    lua_closure();
+}
+
+typedef int (*lua_closure_with_pointer_t)(int, int*);
+int call_closure_with_pointer(lua_closure_with_pointer_t lua_closure) {
+    int b = 24;
+    return lua_closure(12, &b) * 2;
+}
diff --git a/tests/ffi/external_math/addInt.luau b/tests/ffi/external_math/addInt.luau
new file mode 100644
index 0000000..d63f313
--- /dev/null
+++ b/tests/ffi/external_math/addInt.luau
@@ -0,0 +1,9 @@
+local callableWrapper = require("../utils/callableWrapper")
+local ffi = require("@lune/ffi")
+local lib = require("../utils/compile")("./tests/ffi/external_math/lib.c")
+local c = ffi.c
+
+local addInt = callableWrapper(lib:find("add_int"), { c.int, c.int }, c.int)
+local result = addInt(100, 200)
+
+assert(result == 300, `test_addInt failed. result expected 300, got {result}`)
diff --git a/tests/ffi/external_math/lib.c b/tests/ffi/external_math/lib.c
new file mode 100644
index 0000000..b518230
--- /dev/null
+++ b/tests/ffi/external_math/lib.c
@@ -0,0 +1,7 @@
+int add_int(int a, int b) {
+    return a + b;
+}
+
+int mul_int(int a, int b) {
+    return a * b;
+}
diff --git a/tests/ffi/external_math/mulInt.luau b/tests/ffi/external_math/mulInt.luau
new file mode 100644
index 0000000..e14a86b
--- /dev/null
+++ b/tests/ffi/external_math/mulInt.luau
@@ -0,0 +1,8 @@
+local callableWrapper = require("../utils/callableWrapper")
+local ffi = require("@lune/ffi")
+local lib = require("../utils/compile")("./tests/ffi/external_math/lib.c")
+local c = ffi.c
+
+local mulInt = callableWrapper(lib:find("mul_int"), { c.int, c.int }, c.int)
+local result = mulInt(100, 200)
+assert(result == 20000, `test_mulInt failed. result expected 20000, got {result}`)
diff --git a/tests/ffi/external_pointer/lib.c b/tests/ffi/external_pointer/lib.c
new file mode 100644
index 0000000..7d7440a
--- /dev/null
+++ b/tests/ffi/external_pointer/lib.c
@@ -0,0 +1,7 @@
+void pointer_write(int *a) {
+    *a = 123;
+}
+
+int pointer_read(int *a) {
+    return *a;
+}
diff --git a/tests/ffi/external_pointer/pointerRead.luau b/tests/ffi/external_pointer/pointerRead.luau
new file mode 100644
index 0000000..e2490a3
--- /dev/null
+++ b/tests/ffi/external_pointer/pointerRead.luau
@@ -0,0 +1,8 @@
+local callableWrapper = require("../utils/callableWrapper")
+local ffi = require("@lune/ffi")
+local lib = require("../utils/compile")("./tests/ffi/external_pointer/lib.c")
+local c = ffi.c
+
+local pointerRead = callableWrapper(lib:find("pointer_read"), { c.int:ptr() }, c.int)
+local result = pointerRead(c.int:box(123):ref():ref())
+assert(result == 123, `pointerRead failed. result expected 123, got {result}`)
diff --git a/tests/ffi/external_pointer/pointerWrite.luau b/tests/ffi/external_pointer/pointerWrite.luau
new file mode 100644
index 0000000..72a9b40
--- /dev/null
+++ b/tests/ffi/external_pointer/pointerWrite.luau
@@ -0,0 +1,9 @@
+local ffi = require("@lune/ffi")
+local lib = require("../utils/compile")("./tests/ffi/external_pointer/lib.c")
+local c = ffi.c
+
+local pointerWrite = c.fn({ c.int:ptr() }, c.void):callable(lib:find("pointer_write"))
+local aBox = ffi.box(c.int.size)
+pointerWrite(nil, aBox:ref():ref())
+local result = c.int:readData(aBox)
+assert(result == 123, `pointerWrite failed. result expected 123, got {result}`)
diff --git a/tests/ffi/external_print/helloWorld.luau b/tests/ffi/external_print/helloWorld.luau
new file mode 100644
index 0000000..665283b
--- /dev/null
+++ b/tests/ffi/external_print/helloWorld.luau
@@ -0,0 +1,5 @@
+local ffi = require("@lune/ffi")
+local lib = require("../utils/compile")("./tests/ffi/external_print/lib.c")
+local c = ffi.c
+
+c.fn({}, c.void):callable(lib:find("hello_world"))(nil)
diff --git a/tests/ffi/external_print/lib.c b/tests/ffi/external_print/lib.c
new file mode 100644
index 0000000..d650492
--- /dev/null
+++ b/tests/ffi/external_print/lib.c
@@ -0,0 +1,5 @@
+#include<stdio.h>
+
+void hello_world() {
+    printf("Hello world from external function!");
+}
diff --git a/tests/ffi/external_struct/ab.luau b/tests/ffi/external_struct/ab.luau
new file mode 100644
index 0000000..114b74c
--- /dev/null
+++ b/tests/ffi/external_struct/ab.luau
@@ -0,0 +1,12 @@
+local callableWrapper = require("../utils/callableWrapper")
+local ffi = require("@lune/ffi")
+local lib = require("../utils/compile")("./tests/ffi/external_struct/lib.c")
+local c = ffi.c
+
+local argStructInfo = c.struct({ c.int, c.int:ptr() })
+local resultStructInfo = c.struct({ c.int, c.int })
+
+local ab = callableWrapper(lib:find("ab"), { argStructInfo }, resultStructInfo)
+local result = ab({ 100, c.int:box(200):ref() } :: { any })
+assert(result[1] == 300, `ab failed. result expected 300, got {result[1]}`)
+assert(result[2] == 20000, `ab failed. result expected 300, got {result[2]}`)
diff --git a/tests/ffi/external_struct/lib.c b/tests/ffi/external_struct/lib.c
new file mode 100644
index 0000000..f68a067
--- /dev/null
+++ b/tests/ffi/external_struct/lib.c
@@ -0,0 +1,14 @@
+typedef struct {
+    int a;
+    int* b;
+} ArgStruct;
+
+typedef struct {
+    int sum;
+    int mul;
+} ResultStruct;
+
+ResultStruct ab(ArgStruct t) {
+    ResultStruct result = { t.a+ * t.b, t.a * (*t.b) };
+    return result;
+}
diff --git a/tests/ffi/free.luau b/tests/ffi/free.luau
new file mode 100644
index 0000000..5ab91da
--- /dev/null
+++ b/tests/ffi/free.luau
@@ -0,0 +1,18 @@
+--!nocheck
+--!nolint
+local ffi = require("@lune/ffi")
+
+local box = ffi.box(ffi.i32.size)
+local ref = box:leak()
+
+box = nil
+
+collectgarbage("collect")
+collectgarbage("collect")
+collectgarbage("collect")
+
+ffi.free(ref)
+
+collectgarbage("collect")
+collectgarbage("collect")
+collectgarbage("collect")
diff --git a/tests/ffi/isInteger.luau b/tests/ffi/isInteger.luau
new file mode 100644
index 0000000..a856df0
--- /dev/null
+++ b/tests/ffi/isInteger.luau
@@ -0,0 +1,7 @@
+local ffi = require("@lune/ffi")
+
+local int = 0b1
+local float = 0.5
+
+assert(ffi.isInteger(int) == true, "ffi.isInteger(int) == true assersion failed")
+assert(ffi.isInteger(float) == false, "ffi.isInteger(float) == false assersion failed")
diff --git a/tests/ffi/pretty_print/arr.luau b/tests/ffi/pretty_print/arr.luau
new file mode 100644
index 0000000..4b78d5e
--- /dev/null
+++ b/tests/ffi/pretty_print/arr.luau
@@ -0,0 +1,6 @@
+local ffi = require("@lune/ffi")
+local c = ffi.c
+
+assert(typeof(c.int:arr(5)) :: string == "CArrInfo")
+assert(tostring(c.int:arr(5)) == " int, length = 5 ")
+assert(tostring(c.int:ptr():arr(5)) == " <CPtrInfo(int)>, length = 5 ")
diff --git a/tests/ffi/pretty_print/box.luau b/tests/ffi/pretty_print/box.luau
new file mode 100644
index 0000000..e971434
--- /dev/null
+++ b/tests/ffi/pretty_print/box.luau
@@ -0,0 +1,4 @@
+local ffi = require("@lune/ffi")
+local half_back = 0b_0000_0000_0000_0000_0000_0000_1111_1111
+
+print(ffi.i32:box(half_back))
diff --git a/tests/ffi/pretty_print/fn.luau b/tests/ffi/pretty_print/fn.luau
new file mode 100644
index 0000000..d20326d
--- /dev/null
+++ b/tests/ffi/pretty_print/fn.luau
@@ -0,0 +1,13 @@
+local ffi = require("@lune/ffi")
+local c = ffi.c
+
+assert(typeof(c.fn({ c.int }, c.int)) :: string == "CFnInfo")
+assert(tostring(c.fn({ c.int }, c.int)) == " (int) -> int ")
+assert(tostring(c.fn({ c.int, ffi.f32 }, c.int)) == " (int, f32) -> int ")
+assert(tostring(c.fn({ c.int:ptr() }, c.int)) == " (<CPtrInfo(int)>) -> int ")
+assert(tostring(c.fn({ c.int }, c.int:ptr())) == " (int) -> <CPtrInfo(int)> ")
+assert(tostring(c.fn({ c.int:ptr() }, c.int:ptr())) == " (<CPtrInfo(int)>) -> <CPtrInfo(int)> ")
+assert(
+	tostring(c.fn({ c.int:ptr(), c.int:ptr() }, c.int:ptr()))
+		== " (<CPtrInfo(int)>, <CPtrInfo(int)>) -> <CPtrInfo(int)> "
+)
diff --git a/tests/ffi/pretty_print/ptr.luau b/tests/ffi/pretty_print/ptr.luau
new file mode 100644
index 0000000..fd3da1d
--- /dev/null
+++ b/tests/ffi/pretty_print/ptr.luau
@@ -0,0 +1,6 @@
+local ffi = require("@lune/ffi")
+local c = ffi.c
+
+assert(typeof(c.int:ptr()) :: string == "CPtrInfo")
+assert(tostring(c.int:ptr()) == "int")
+assert(tostring(c.int:arr(5):ptr()) == " <CArrInfo( int, length = 5 )> ")
diff --git a/tests/ffi/pretty_print/struct.luau b/tests/ffi/pretty_print/struct.luau
new file mode 100644
index 0000000..124d412
--- /dev/null
+++ b/tests/ffi/pretty_print/struct.luau
@@ -0,0 +1,8 @@
+local ffi = require("@lune/ffi")
+local c = ffi.c
+
+assert(typeof(c.struct({ c.int, c.char })) :: string == "CStructInfo")
+assert(
+	tostring(c.struct({ c.int, c.char:ptr() }))
+		== ` int, <CPtrInfo(char)>, size = {c.struct({ c.int, c.char:ptr() }).size} `
+)
diff --git a/tests/ffi/pretty_print/type.luau b/tests/ffi/pretty_print/type.luau
new file mode 100644
index 0000000..af86dcc
--- /dev/null
+++ b/tests/ffi/pretty_print/type.luau
@@ -0,0 +1,6 @@
+local ffi = require("@lune/ffi")
+local c = ffi.c
+
+assert(typeof(c.int) :: string == "CTypeInfo")
+assert(tostring(c.int) == "int")
+assert(tostring(ffi.i32) == "i32")
diff --git a/tests/ffi/readBoundary.luau b/tests/ffi/readBoundary.luau
new file mode 100644
index 0000000..94c4ff3
--- /dev/null
+++ b/tests/ffi/readBoundary.luau
@@ -0,0 +1,51 @@
+local ffi = require("@lune/ffi")
+local ok
+
+-- Case1: Success
+ok = pcall(function()
+	local box = ffi.u8:box(1)
+	ffi.u8:readData(box)
+end)
+assert(ok, "assersion failed, Case1 should success")
+
+-- Case2: Fail
+ok = pcall(function()
+	local box = ffi.u8:box(1)
+	ffi.u16:readData(box)
+end)
+assert(not ok, "assersion failed, Case2 should fail")
+
+-- Case3: Success
+ok = pcall(function()
+	local box = ffi.box(ffi.u8.size * 2)
+	ffi.u16:readData(box)
+end)
+assert(ok, "assersion failed, Case3 should success")
+
+-- Case4: Success
+ok = pcall(function()
+	local box = ffi.box(ffi.u8.size * 2)
+	ffi.u8:readData(box, ffi.u8.size)
+end)
+assert(ok, "assersion failed, Case4 should success")
+
+-- Case5: Fail
+ok = pcall(function()
+	local box = ffi.u8:box(1):ref()
+	ffi.u16:readData(box)
+end)
+assert(not ok, "assersion failed, Case5 should fail")
+
+-- Case6: Success
+ok = pcall(function()
+	local box = ffi.box(ffi.u8.size * 2):ref()
+	ffi.u16:readData(box)
+end)
+assert(ok, "assersion failed, Case6 should success")
+
+-- Case7: Fail
+ok = pcall(function()
+	local box = ffi.box(ffi.u8.size * 2):ref(ffi.u16.size)
+	ffi.u16:readData(box)
+end)
+assert(not ok, "assersion failed, Case7 should fail")
diff --git a/tests/ffi/stringReadWrite.luau b/tests/ffi/stringReadWrite.luau
new file mode 100644
index 0000000..4351047
--- /dev/null
+++ b/tests/ffi/stringReadWrite.luau
@@ -0,0 +1,34 @@
+local ffi = require("@lune/ffi")
+local ok
+
+local str = "hello world"
+local strbox = ffi.box(#str):writeString(str)
+assert(strbox:readString(#str) == str, "String read write assersion failed")
+
+-- Case1: Fail
+ok = pcall(function()
+	local box = ffi.box(2)
+	box:readString(10)
+end)
+assert(not ok, "assersion failed, Case1 should fail")
+
+-- Case2: Fail
+ok = pcall(function()
+	local box = ffi.box(2)
+	box:writeString("hello world")
+end)
+assert(not ok, "assersion failed, Case2 should fail")
+
+-- Case3: Fail
+ok = pcall(function()
+	local box = ffi.box(2):ref()
+	box:readString(10)
+end)
+assert(not ok, "assersion failed, Case3 should fail")
+
+-- Case4: Fail
+ok = pcall(function()
+	local box = ffi.box(2):ref()
+	box:writeString("hello world")
+end)
+assert(not ok, "assersion failed, Case4 should fail")
diff --git a/tests/ffi/types/arr.luau b/tests/ffi/types/arr.luau
new file mode 100644
index 0000000..448fff3
--- /dev/null
+++ b/tests/ffi/types/arr.luau
@@ -0,0 +1,19 @@
+local ffi = require("@lune/ffi")
+local ok
+
+local arr = ffi.i32:arr(4)
+
+assert(rawequal(arr.length, 4), "length assersion failed, arr.length should be 4")
+assert(rawequal(arr.inner, ffi.i32), "inner assersion failed, arr.inner should be ffi.i32")
+
+-- offset(2) success
+ok = pcall(function()
+	arr:offset(2)
+end)
+assert(ok, "assersion failed, arr:offset(2) should success")
+
+-- offset(4) success
+ok = pcall(function()
+	arr:offset(4)
+end)
+assert(not ok, "assersion failed, arr:offset(4) should fail")
diff --git a/tests/ffi/types/ptr.luau b/tests/ffi/types/ptr.luau
new file mode 100644
index 0000000..05cdf13
--- /dev/null
+++ b/tests/ffi/types/ptr.luau
@@ -0,0 +1,31 @@
+local ffi = require("@lune/ffi")
+
+-- ptr size test
+assert(
+	ffi.i32:ptr().size == ffi.i64:ptr().size,
+	"size assersion failed, size of pointer should be same with each other (ffi.i32:ptr().size == ffi.i64:ptr().size)"
+)
+
+-- inner test
+local i32ptr = ffi.i32:ptr()
+assert(
+	rawequal(ffi.i32, i32ptr.inner),
+	"inner assersion failed, inner field must be same with their parent"
+		.. " (ffi.i32 == ffi.i32:ptr().inner)"
+)
+assert(
+	rawequal(i32ptr, i32ptr:ptr().inner),
+	"inner assersion failed, inner field must be same with their parent"
+		.. " (i32ptr == i32ptr:ptr().inner)"
+)
+assert(
+	rawequal(i32ptr, i32ptr:ptr().inner:ptr().inner:ptr().inner),
+	"inner assersion failed, inner field must be same with their parent"
+		.. " (i32ptr == i32ptr:ptr().inner:ptr().inner:ptr().inner)"
+)
+
+-- deep ptr test
+local ok, err = pcall(function()
+	i32ptr:ptr():ptr():ptr():ptr():ptr():ptr():ptr()
+end)
+assert(ok, "deep ptr assersion failed\n" .. (err or ""))
diff --git a/tests/ffi/types/struct.luau b/tests/ffi/types/struct.luau
new file mode 100644
index 0000000..10edd4a
--- /dev/null
+++ b/tests/ffi/types/struct.luau
@@ -0,0 +1,20 @@
+local ffi = require("@lune/ffi")
+local ok
+
+local i32ptr = ffi.i32:ptr()
+local struct = ffi.c.struct({ i32ptr, ffi.i32 })
+
+assert(rawequal(struct:field(0), i32ptr), "Struct get field failed")
+assert(rawequal(struct:field(1), ffi.i32), "Struct get field failed")
+
+-- offset(2) should fail
+ok = pcall(function()
+	struct:offset(2)
+end)
+assert(not ok, "assersion failed, struct:offset(2) should fail")
+
+-- field(2) should fail
+ok = pcall(function()
+	struct:field(2)
+end)
+assert(not ok, "assersion failed, struct:field(2) should fail")
diff --git a/tests/ffi/utils/callableWrapper.luau b/tests/ffi/utils/callableWrapper.luau
new file mode 100644
index 0000000..ffa9597
--- /dev/null
+++ b/tests/ffi/utils/callableWrapper.luau
@@ -0,0 +1,38 @@
+--!nocheck
+local ffi = require("@lune/ffi")
+
+local function callableWrapper(
+	functionRef: ffi.RefData,
+	argTypeList: { ffi.CTypes },
+	retType: ffi.CTypes
+): (...any) -> any
+	local callable = ffi.c.fn(argTypeList, retType):callable(functionRef)
+
+	return function(...)
+		local argValues = table.create(#argTypeList + 1)
+
+		local resultBox
+		if retType ~= ffi.c.void then
+			resultBox = ffi.box(retType.size)
+		end
+		argValues[1] = resultBox
+
+		for index, argType in argTypeList do
+			local arg = select(index, ...)
+			if type(arg) == "userdata" then
+				argValues[index + 1] = arg
+			else
+				argValues[index + 1] = argType:box(arg):ref()
+			end
+		end
+
+		callable(table.unpack(argValues, 1, #argTypeList + 1))
+
+		if retType == ffi.c.void then
+			return nil
+		end
+		return retType:readData(resultBox)
+	end
+end
+
+return callableWrapper
diff --git a/tests/ffi/utils/compile.luau b/tests/ffi/utils/compile.luau
new file mode 100644
index 0000000..624ddaf
--- /dev/null
+++ b/tests/ffi/utils/compile.luau
@@ -0,0 +1,25 @@
+local ffi = require("@lune/ffi")
+local process = require("@lune/process")
+
+local function getLibSuffix(): string
+	if process.os == "linux" then
+		return "so"
+	elseif process.os == "windows" then
+		return "dll"
+	elseif process.os == "macos" then
+		return "dylib"
+	end
+	error("Unknown OS")
+end
+
+local function compile(file: string): ffi.LibData
+	local out = file:gsub("%.c$", "." .. getLibSuffix())
+	local gcc = process.exec("gcc", { "-shared", "-o", out, "-fPIC", file })
+	if not gcc.ok then
+		error("Failed to execute gcc command\n" .. gcc.stdout .. gcc.stderr)
+	end
+
+	return ffi.open(out)
+end
+
+return compile
diff --git a/tests/ffi/utils/libSuffix.ts b/tests/ffi/utils/libSuffix.ts
new file mode 100644
index 0000000..41dc281
--- /dev/null
+++ b/tests/ffi/utils/libSuffix.ts
@@ -0,0 +1,13 @@
+export let libSuffix = "";
+// @ts-ignore
+switch (Deno.build.os) {
+  case "windows":
+    libSuffix = "dll";
+    break;
+  case "darwin":
+    libSuffix = "dylib";
+    break;
+  case "linux":
+    libSuffix = "so";
+    break;
+}
diff --git a/tests/ffi/utils/proc_clock/deno.ts b/tests/ffi/utils/proc_clock/deno.ts
new file mode 100644
index 0000000..23ea8d4
--- /dev/null
+++ b/tests/ffi/utils/proc_clock/deno.ts
@@ -0,0 +1,27 @@
+import { libSuffix } from "../libSuffix.ts";
+
+const library_file = "./tests/ffi/utils/proc_clock/lib."+libSuffix;
+// @ts-ignore
+let library = Deno.dlopen(library_file, {
+    sizeof_clock: {
+        parameters: [],
+        result: "i32",
+    },
+});
+const sizeof_clock = library.symbols.sizeof_clock();
+const type_clock_t = "u" + (sizeof_clock * 8);
+library.close();
+// @ts-ignore
+library = Deno.dlopen(library_file, {
+    get_clock: {
+        parameters: [],
+        result: type_clock_t,
+    },
+    get_offset: {
+        parameters: [type_clock_t, type_clock_t],
+        result: "f64",
+    },
+});
+
+export const get_clock = library.symbols.get_clock;
+export const get_offset = library.symbols.get_offset;
diff --git a/tests/ffi/utils/proc_clock/init.luau b/tests/ffi/utils/proc_clock/init.luau
new file mode 100644
index 0000000..8877a4c
--- /dev/null
+++ b/tests/ffi/utils/proc_clock/init.luau
@@ -0,0 +1,46 @@
+-- FIXME: in windows, we need another library to get process cpu time
+
+local ffi = require("@lune/ffi")
+local process = require("@lune/process")
+local isWindows = process.os == "windows"
+local c = ffi.c
+
+local procClock = {}
+
+local lib = require("../compile")("./tests/ffi/utils/proc_clock/lib.c")
+
+-- sizeof_clock
+local sizeofClock = c.fn({}, c.int):callable(lib:find("sizeof_clock"))
+function procClock.sizeofClock(): number
+	local result = ffi.box(c.int.size)
+	sizeofClock(result)
+	return c.int:readData(result)
+end
+-- get_clock
+local clock_t = if isWindows then ffi.f32 else ffi["u" .. (procClock.sizeofClock() * 8)]
+assert(clock_t, "clock_t is unknown type")
+procClock.getClock = (
+	if isWindows
+		then function(clock: ffi.BoxData | ffi.RefData)
+			ffi.f32:writeData(clock, os.clock())
+		end
+		else c.fn({}, clock_t):callable(lib:find("get_clock"))
+) :: (ffi.BoxData | ffi.RefData) -> ()
+
+-- get_offset
+local getOffset: (ffi.BoxData, ffi.RefData, ffi.RefData) -> () = if isWindows
+	then function(result: ffi.BoxData, before: ffi.RefData, after: ffi.RefData)
+		ffi.f64:writeData(result, (ffi.f32:readData(after) - ffi.f32:readData(before)))
+	end
+	else c.fn({ clock_t, clock_t }, ffi.f64):callable(lib:find("get_offset"))
+function procClock.getOffset(before: ffi.BoxData, after: ffi.BoxData): number
+	local result = ffi.box(ffi.f64.size)
+	getOffset(result, before:ref(), after:ref())
+	return ffi.f64:readData(result)
+end
+
+function procClock.newBox(): (ffi.BoxData, ffi.BoxData)
+	return ffi.box(clock_t.size), ffi.box(clock_t.size)
+end
+
+return procClock
diff --git a/tests/ffi/utils/proc_clock/lib.c b/tests/ffi/utils/proc_clock/lib.c
new file mode 100644
index 0000000..bef49b0
--- /dev/null
+++ b/tests/ffi/utils/proc_clock/lib.c
@@ -0,0 +1,12 @@
+#include <time.h>
+clock_t get_clock() {
+    return clock();
+}
+
+int sizeof_clock() {
+    return sizeof(clock_t);
+}
+
+double get_offset(clock_t before, clock_t after) {
+    return (double)(after - before) / CLOCKS_PER_SEC;
+}
diff --git a/tests/ffi/writeBoundary.luau b/tests/ffi/writeBoundary.luau
new file mode 100644
index 0000000..afb2b16
--- /dev/null
+++ b/tests/ffi/writeBoundary.luau
@@ -0,0 +1,45 @@
+local ffi = require("@lune/ffi")
+local c = ffi.c
+local ok
+
+-- Case1: Fail
+ok = pcall(function()
+	local box = ffi.box(c.int.size - 1)
+	c.int:writeData(box, 10)
+end)
+assert(not ok, "assersion failed, Case1 should fail")
+
+-- Case2: Success
+ok = pcall(function()
+	local box = ffi.box(c.int.size)
+	c.int:writeData(box, 10)
+end)
+assert(ok, "assersion failed, Case2 should success")
+
+-- Case3: Success
+ok = pcall(function()
+	local box = ffi.box(c.int.size * 2)
+	c.int:writeData(box, 10, c.int.size)
+end)
+assert(ok, "assersion failed, Case3 should success")
+
+-- Case4: Fail
+ok = pcall(function()
+	local box = ffi.box(c.int.size * 2)
+	c.int:writeData(box, 10, c.int.size * 2)
+end)
+assert(not ok, "assersion failed, Case4 should fail")
+
+-- Case5: Success
+ok = pcall(function()
+	local box = ffi.box(c.int.size * 2):ref()
+	c.int:writeData(box, 10, c.int.size)
+end)
+assert(ok, "assersion failed, Case5 should success")
+
+-- Case6: Fail
+ok = pcall(function()
+	local box = ffi.box(c.int.size * 2):ref()
+	c.int:writeData(box, 10, c.int.size * 2)
+end)
+assert(not ok, "assersion failed, Case6 should fail")
diff --git a/types/ffi.luau b/types/ffi.luau
new file mode 100644
index 0000000..6cc8fc1
--- /dev/null
+++ b/types/ffi.luau
@@ -0,0 +1,1224 @@
+--[=[
+	@class FFI
+
+	Built-in library for foreign function interface.
+
+	### Example usage
+	lib.c:
+	```c
+	int add(int a, int b) {
+		return a + b;
+	}
+	```
+	init.luau:
+	```lua
+	local ffi = require("@lune/ffi")
+
+	-- Create function signature
+	local addSignature = ffi.c.fn({ ffi.c.int, ffi.c.int }, ffi.c.int)
+
+	-- Load library
+	local lib = ffi.open("./lib.so")
+
+	-- Get symbol from library
+	local addSymbol = lib:find("add")
+
+	-- Create CallableData
+	local add = addSignature:callable(addSymbol)
+
+	-- Create result box and arguments
+	local result = ffi.box(ffi.c.int.size)
+	local a = ffi.c.int:box(1)
+	local b = ffi.c.int:box(2)
+
+	-- Call external function
+	add(result, a:ref(), b:ref())
+
+	-- Get number from result
+	print(ffi.c.int:readData(result))
+	```
+]=]
+local ffi = {}
+
+--[=[
+	@class C
+
+	Namespace for compile time sized c types.
+]=]
+local c = {}
+ffi.c = c
+
+--#region Data
+--[=[
+	@class RefData
+
+	A user manageable memory reference.
+
+	It can be GCed, But it doesn't free the referenced memory.
+]=]
+export type RefData = {
+	--[=[
+		@within RefData
+		@tag Method
+		@method deref
+
+		Create a RefData by dereference this reference.
+		The created reference has no boundaries and has no restrictions.
+
+		This method is unsafe.
+
+		@return A dereferenced `RefData`
+	]=]
+	deref: (self: RefData) -> RefData,
+	--[=[
+		@within RefData
+		@tag Method
+		@method offset
+
+		Create a reference with specific offset from this reference.
+
+		The created reference can be GCed and holds same data.
+
+		@param offset Create a reference at the given offset
+		@return A offseted reference
+	]=]
+	offset: (self: RefData, offset: number) -> RefData,
+	--[=[
+		@within RefData
+		@tag Method
+		@method ref
+
+		Create a reference of this reference.
+
+		The created reference keeps the target reference from being garbage collected until created reference itself is collected.
+
+		@return A reference of this reference
+	]=]
+	ref: (self: RefData) -> RefData,
+	--[=[
+		@within RefData
+		@tag Method
+		@method leak
+
+		Create a reference of this reference after leaking it.
+
+		GC doesn't manage destruction after this action. You must free it later.
+
+		@return A reference of this reference
+	]=]
+	leak: (self: RefData) -> RefData,
+	--[=[
+		@within RefData
+		@tag Method
+		@method isNull
+
+		Check reference is null or not.
+
+		@return Whether reference is null or not
+	]=]
+	isNull: (self: RefData) -> boolean,
+	--[=[
+		@within RefData
+		@tag Method
+		@method copyFrom
+
+		Copy content from another data with specific length.
+
+		@param src The source data
+		@param length The amount of data to copy, in bytes
+		@param dstOffset The offset in the destination where the content will be pasted
+		@param srcOffset The offset in the source data from where the content will be copied
+		@return `RefData` itself for convenience
+	]=]
+	copyFrom: (
+		self: RefData,
+		src: BoxData | RefData,
+		length: number,
+		dstOffset: number,
+		srcOffset: number
+	) -> RefData,
+	--[=[
+		@within RefData
+		@tag Method
+		@method readString
+
+		Read string from data with specific length without null termination.
+
+		@param length The amount of data to read, in bytes
+		@param offset Offset to read string from
+		@return A string
+	]=]
+	readString: (self: RefData, length: number, offset: number?) -> string,
+	--[=[
+		@within RefData
+		@tag Method
+		@method writeString
+
+		Write string into data without null termination.
+
+		@param src The source string
+		@param length The amount of data to write, in bytes
+		@param dstOffset The offset in the destination where the content will be pasted
+		@param srcOffset The offset in the source string from where the content will be copied
+		@return `RefData` itself for convenience
+	]=]
+	writeString: (
+		self: RefData,
+		src: string,
+		length: number?,
+		dstOffset: number?,
+		srcOffset: number?
+	) -> RefData,
+}
+
+--[=[
+	@class BoxData
+
+	A user manageable heap memory.
+]=]
+export type BoxData = {
+	--[=[
+		@within BoxData
+		@tag Field
+		@field size
+
+		The size of the box.
+	]=]
+	size: number,
+
+	--[=[
+		@within BoxData
+		@tag Method
+		@method zero
+
+		Fill the box with zero.
+
+		@return `BoxData` itself for convenience
+	]=]
+	zero: (self: BoxData) -> BoxData,
+	--[=[
+		@within BoxData
+		@tag Method
+		@method leak
+
+		Create a reference of the box after leaking it.
+
+		GC doesn't manage destruction after this action. You must free it later.
+
+		@param offset Create a reference at the given offset
+		@return A reference of the box
+	]=]
+	leak: (self: BoxData, offset: number?) -> RefData,
+	--[=[
+		@within BoxData
+		@tag Method
+		@method ref
+
+		Create a reference of the box.
+
+		The created reference keeps the box from being garbage collected until the reference itself is collected.
+
+		@param offset Create a reference at the given offset
+		@return A reference of the box
+	]=]
+	ref: (self: BoxData, offset: number?) -> RefData,
+	--[=[
+		@within BoxData
+		@tag Method
+		@method copyFrom
+
+		Copy content from another data with specific length.
+
+		@param src The source data
+		@param length The amount of data to copy, in bytes
+		@param dstOffset The offset in the destination where the content will be pasted
+		@param srcOffset The offset in the source data from where the content will be copied
+		@return `BoxData` itself for convenience
+	]=]
+	copyFrom: (
+		self: BoxData,
+		src: BoxData | RefData,
+		length: number,
+		dstOffset: number,
+		srcOffset: number
+	) -> BoxData,
+	--[=[
+		@within BoxData
+		@tag Method
+		@method readString
+
+		Read string from data with specific length without null termination.
+
+		@param length The amount of data to read, in bytes
+		@param offset Offset to read string from
+		@return A string
+	]=]
+	readString: (self: BoxData, length: number, offset: number?) -> string,
+	--[=[
+		@within BoxData
+		@tag Method
+		@method writeString
+
+		Write string into data without null termination.
+
+		@param src The source string
+		@param length The amount of data to write, in bytes
+		@param dstOffset The offset in the destination where the content will be pasted
+		@param srcOffset The offset in the source string from where the content will be copied
+		@return `BoxData` itself for convenience
+	]=]
+	writeString: (
+		self: BoxData,
+		src: string,
+		length: number?,
+		dstOffset: number?,
+		srcOffset: number?
+	) -> BoxData,
+}
+
+--[=[
+	@class LibData
+
+	A dynamic opened library handle.
+]=]
+export type LibData = {
+	--[=[
+		@within LibData
+		@tag Method
+		@method find
+
+		Find a symbol from the dynamic library.
+
+		@param sym The name of the symbol
+		@return A `Ref` of the found symbol
+	]=]
+	find: (self: LibData, sym: string) -> RefData,
+}
+
+--[=[
+	@class CallableData
+
+	A callable external function.
+
+	To call external function, provide memory for save the return value and references for the arguments.
+
+	If return type is `void`, pass `nil`.
+]=]
+export type CallableData = (
+	ret: (RefData | BoxData)?,
+	...RefData
+) -> () & {
+	-- apply: (self: Callable, args: Args)->AppliedCallable,
+}
+-- export type AppliedCallable = ()->()
+
+--[=[
+	@class ClosureData
+
+	A reference that holds lua function.
+]=]
+export type ClosureData = {
+	--[=[
+		@within ClosureData
+		@tag Method
+		@method ref
+
+		Create a reference of the closure. usually can be used for passing function pointer as argument.
+
+		The created reference keeps the closure from being garbage collected until the reference itself is collected.
+
+		@return A reference of the closure
+	]=]
+	ref: (self: ClosureData) -> RefData,
+}
+--#endregion Data
+
+--#region C ABI Type Infos
+-- NOTE: T is a unique identifier for the `CType` and R is the closest Lua type.
+export type CTypeInfo<T, R> = {
+	--[=[
+		@within CTypeInfo
+		@tag Field
+		@field size
+
+		The size of the type in bytes.
+	]=]
+	size: number,
+	--[=[
+		@within CTypeInfo
+		@tag Field
+		@field signedness
+
+		The signedness of the type.
+	]=]
+	signedness: boolean,
+
+	-- Subtype
+	--[=[
+		@within CTypeInfo
+		@tag Method
+		@Method ptr
+
+		Create a pointer subtype.
+
+		@return A pointer subtype
+	]=]
+	ptr: (self: CTypeInfo<T, R>) -> CPtrInfo<CTypeInfo<T, R>>,
+	--[=[
+		@within CTypeInfo
+		@tag Method
+		@Method arr
+
+		Create an array subtype with specific length.
+
+		@param length The length of the array
+		@return An array subtype
+	]=]
+	arr: (self: CTypeInfo<T, R>, length: number) -> CArrInfo<CTypeInfo<T, R>, R>,
+
+	-- Create/Read/Write/Copy
+	--[=[
+		@within CTypeInfo
+		@tag Method
+		@Method box
+
+		Create a box with initial values.
+
+		@param table The array of element values
+		@return A box
+	]=]
+	box: (self: CTypeInfo<T, R>, val: R) -> BoxData,
+	--[=[
+		@within CTypeInfo
+		@tag Method
+		@method readData
+
+		Read a lua value from reference or box.
+
+		@param target Target to read data from
+		@param offset Offset to read data from
+		@return A lua value
+	]=]
+	readData: (self: CTypeInfo<T, R>, target: RefData | BoxData, offset: number?) -> R,
+	--[=[
+		@within CTypeInfo
+		@tag Method
+		@method writeData
+
+		Write a lua value into reference or box.
+
+		@param target Target to write data into
+		@param value Lua data to write
+		@param offset Offset to write data into
+	]=]
+	writeData: (self: CTypeInfo<T, R>, target: RefData | BoxData, value: R, offset: number?) -> (),
+	--[=[
+		@within CTypeInfo
+		@tag Method
+		@Method copyData
+	
+		Copy values ​​from the source and paste them into the target.
+
+		@param dst Where the content will be pasted
+		@param src The source data
+		@param dstOffset The offset in the destination where the content will be pasted
+		@param srcOffset The offset in the source data from where the content will be copied
+	]=]
+	copyData: (
+		self: CTypeInfo<T, R>,
+		dst: RefData | BoxData,
+		src: RefData | BoxData,
+		dstOffset: number?,
+		srcOffset: number?
+	) -> (),
+	--[=[
+		@within CTypeInfo
+		@tag Method
+		@Method stringifyData
+
+		Stringify data. Useful when output numbers, which Luau can't handle.
+	
+		@param target The target data
+		@param offset Offset to stringify data from
+	]=]
+	stringifyData: (self: CTypeInfo<T, R>, target: RefData | BoxData, offset: number?) -> string,
+
+	-- ETC
+	-- FIXME: recursive types; 'intoType' should be CTypes
+	--[=[
+		@within CTypeInfo
+		@tag Method
+		@Method cast
+	
+		Casting data to different type.
+
+		May result in loss of precision.
+
+		@param intoType The target type to convert to
+		@param fromData Source data to be converted
+		@param intoData Target to write converted data into
+		@param fromOffset The offset in the source data
+		@param intoOffset The offset in the destination
+	]=]
+	cast: (
+		self: CTypeInfo<T, R>,
+		intoType: any,
+		fromData: RefData | BoxData,
+		intoData: RefData | BoxData,
+		fromOffset: number?,
+		intoOffset: number?
+	) -> (),
+} & { ["__phantom"]: T }
+type NumCType<T> = CTypeInfo<T, (number | any)>
+
+export type CPtrInfo<T> = {
+	--[=[
+		@within CPtrInfo
+		@tag Field
+		@field size
+
+		The size of a pointer. should be the same for all pointers.
+
+		Equivalent to `ffi.c.usize.size`.
+	]=]
+	size: number,
+	--[=[
+		@within CPtrInfo
+		@tag Field
+		@field inner
+
+		The inner type of the pointer.
+	]=]
+	inner: T,
+
+	-- Subtype
+	-- FIXME: recursive types; result 'any' should be CArrInfo<CPtrInfo<T>>
+	--[=[
+		@within CPtrInfo
+		@tag Method
+		@Method arr
+
+		Create an array subtype with specific length.
+
+		@param length The length of the array
+		@return An array subtype
+	]=]
+	arr: (self: CPtrInfo<T>, length: number) -> any,
+	-- FIXME: recursive types; result 'any' should be CPtrInfo<CPtrInfo<T>>
+	--[=[
+		@within CPtrInfo
+		@tag Method
+		@Method ptr
+
+		Create a pointer subtype.
+
+		@return A pointer subtype
+	]=]
+	ptr: (self: CPtrInfo<T>) -> any,
+
+	-- Address
+	--[=[
+		@within CPtrInfo
+		@tag Method
+		@Method readRef
+
+		Read address from data, then return RefData.
+
+		Useful when reading pointer fields of structures.
+
+		If the `ref` argument is given, rather than create new RefData, update it.
+
+		@param target Target data to read address from
+		@param offset Offset to read address from
+		@param ref RefData to update
+		@return A lua value
+	]=]
+	readRef: (
+		self: CPtrInfo<T>,
+		target: RefData | BoxData,
+		offset: number?,
+		ref: RefData?
+	) -> RefData,
+	--[=[
+		@within CPtrInfo
+		@tag Method
+		@Method writeRef
+
+		Write address to data.
+	
+		Useful when writing pointer fields of structures.
+
+		@param target Target data to write address into
+		@param ref Memory address to write
+		@param offset Offset to write address into
+	]=]
+	writeRef: (
+		self: CPtrInfo<T>,
+		target: RefData | BoxData,
+		ref: RefData | BoxData | ClosureData,
+		offset: number?
+	) -> (),
+}
+
+--[=[
+	@class CArrInfo
+
+	A c sized array type information. It can be used for sturct field.
+
+	For function arguments, use CPtr instead.
+]=]
+export type CArrInfo<T, R> = {
+	--[=[
+		@within CArrInfo
+		@tag Field
+		@field size
+
+		The total size of the array in bytes.
+	]=]
+	size: number,
+	--[=[
+		@within CArrInfo
+		@tag Field
+		@field length
+
+		The length of the array.
+	]=]
+	length: number,
+	--[=[
+		@within CArrInfo
+		@tag Field
+		@field inner
+
+		The inner element type of the array.
+	]=]
+	inner: T,
+
+	-- Subtype
+	--[=[
+		@within CArrInfo
+		@tag Method
+		@Method ptr
+
+		Create a pointer subtype.
+
+		@return A pointer subtype
+	]=]
+	ptr: (self: CArrInfo<T, R>) -> CPtrInfo<CArrInfo<T, R>>,
+
+	-- Create/Read/Write/Copy
+	--[=[
+		@within CArrInfo
+		@tag Method
+		@Method box
+
+		Create a box with initial values.
+
+		@param table The array of field values
+		@return A box
+	]=]
+	box: (self: CArrInfo<T, R>, table: { T }) -> BoxData,
+	--[=[
+		@within CArrInfo
+		@tag Method
+		@method readData
+
+		Read a lua table from reference or box.
+
+		@param target Target to read data from
+		@param offset Offset to read data from
+		@return A table
+	]=]
+	readData: (self: CArrInfo<T, R>, target: RefData | BoxData, offset: number?) -> { T },
+	--[=[
+		@within CArrInfo
+		@tag Method
+		@method writeData
+
+		Write a lua table into reference or box.
+
+		@param target Target to write data into
+		@param table Lua data to write
+		@param offset Offset to write data into
+	]=]
+	writeData: (
+		self: CArrInfo<T, R>,
+		target: RefData | BoxData,
+		value: { R },
+		target_offset: number?
+	) -> (),
+	--[=[
+		@within CArrInfo
+		@tag Method
+		@Method copyData
+	
+		Copy values ​​from the source and paste them into the target.
+	
+		@param dst Where the content will be pasted
+		@param src The source data
+		@param dstOffset The offset in the dst where the content will be pasted
+		@param srcOffset The offset in the source data from where the content will be copied
+	]=]
+	copyData: (
+		self: CArrInfo<T, R>,
+		dst: RefData | BoxData,
+		src: RefData | BoxData,
+		dstOffset: number?,
+		srcOffset: number?
+	) -> (),
+
+	-- ETC
+	--[=[
+		@within CArrInfo
+		@tag Method
+		@method offset
+
+		Get the byte offset of the field.
+		
+		@param index The element index
+		@return The byte offset
+	]=]
+	offset: (self: CArrInfo<T, R>, index: number) -> number,
+}
+
+--[=[
+	@class CFnInfo
+
+	A C function pointer type information, with function signature.
+
+	For struct field, array element, or function arguments, use `void:ptr()` instead.
+]=]
+export type CFnInfo = {
+	--[=[
+		@within CFnInfo
+		@tag Field
+		@field size
+
+		The size of a function pointer.
+
+		Equivalent to `ffi.c.usize.size`.
+	]=]
+	size: number,
+
+	-- Function
+	--[=[
+		@within CFnInfo
+		@tag Method
+		@method callable
+
+		Create a callable from reference.
+
+		@return A callable
+	]=]
+	callable: (self: CFnInfo, functionRef: RefData) -> CallableData,
+	--[=[
+		@within CFnInfo
+		@tag Method
+		@method closure
+
+		Create a closure from lua function.
+
+		To return some data, lua function should write value into ret reference.
+
+		@return A closure
+	]=]
+	closure: (self: CFnInfo, (ret: RefData, ...RefData) -> ()) -> ClosureData,
+}
+
+--[=[
+	@class CStructInfo
+
+	A c struct type information.
+]=]
+export type CStructInfo = {
+	--[=[
+		@within CStructInfo
+		@tag Field
+		@field size
+
+		The size of a struct, including padding.
+	]=]
+	size: number,
+
+	-- Subtype
+	--[=[
+		@within CSturctInfo
+		@tag Method
+		@method arr
+
+		Create a struct array type.
+
+		@param length The length of the array
+		@return A struct array type
+	]=]
+	arr: (self: CStructInfo, length: number) -> CArrInfo<CStructInfo, { any }>,
+	--[=[
+		@within CSturctInfo
+		@tag Method
+		@method ptr
+
+		Create a struct pointer type.
+
+		@return A struct pointer type
+	]=]
+	ptr: (self: CStructInfo) -> CPtrInfo<CStructInfo>,
+
+	-- Create/Read/Write/Copy
+	--[=[
+		@within CSturctInfo
+		@tag Method
+		@method box
+
+		Create a box with initial value.
+
+		@param table The array of field values
+		@return A box
+	]=]
+	box: (self: CStructInfo, table: { any }) -> BoxData,
+	--[=[
+		@within CSturctInfo
+		@tag Method
+		@method readData
+
+		Read a lua table from reference or box.
+
+		@param target Target to read data from
+		@param offset Offset to read data from
+		@return A table
+	]=]
+	readData: (self: CStructInfo, target: RefData | BoxData, offset: number?) -> { any },
+	--[=[
+		@within CSturctInfo
+		@tag Method
+		@method writeData
+
+		Write a lua table into reference or box.
+
+		@param target Target to write data into
+		@param table Lua data to write
+		@param offset Offset to write data into
+	]=]
+	writeData: (
+		self: CStructInfo,
+		target: RefData | BoxData,
+		table: { any },
+		offset: number?
+	) -> (),
+	--[=[
+		@within CSturctInfo
+		@tag Method
+		@method copyData
+
+		Copy values from the source and paste them into the target.
+
+		@param dst Where the content will be pasted
+		@param src The source data
+		@param dstOffset The offset in the destination where the content will be pasted
+		@param srcOffset The offset in the source data from where the content will be copied
+	]=]
+	copyData: (
+		self: CStructInfo,
+		dst: RefData | BoxData,
+		src: RefData | BoxData,
+		dstOffset: number?,
+		srcOffset: number?
+	) -> (),
+
+	-- ETC
+	--[=[
+		@within CSturctInfo
+		@tag Method
+		@method offset
+
+		Get the field offset.
+
+		@param index The field index
+		@return The byte offset
+	]=]
+	offset: (self: CStructInfo, index: number) -> number,
+	--[=[
+		@within CSturctInfo
+		@tag Method
+		@method field
+
+		Get the field type.
+		
+		@param index The field index
+		@return The field type
+	]=]
+	field: (self: CStructInfo, index: number) -> CTypes,
+}
+
+--[=[
+	@class CVoidInfo
+
+	A type that represents c void. can only be used for the function return type.
+]=]
+export type CVoidInfo = {
+	--[=[
+		@within CVoidInfo
+		@tag Field
+		@field size
+
+		The size of the void type. It is always 0.
+	]=]
+	size: number,
+
+	-- Subtype
+	--[=[
+		@within CVoidInfo
+		@tag Method
+		@method ptr
+
+		Create a generic pointer type.
+
+		@return Generic pointer type, equivalent to `*void` in C.
+	]=]
+	ptr: (self: CVoidInfo) -> CPtrInfo<CVoidInfo>,
+}
+c.void = {} :: CVoidInfo
+--#endregion C ABI Type Infos
+
+--#region Fixed size Rust-style types
+--[=[
+	@prop u8 NumCType
+	@within FFI
+
+	A 8-bit sized unsigned integer, Equivalent to `uint8_t` in `stdint`.
+]=]
+ffi.u8 = {} :: u8
+export type u8 = NumCType<"u8">
+--[=[
+	@prop u16 NumCType
+	@within FFI
+
+	A 16-bit sized unsigned integer, Equivalent to `uint16_t` in `stdint`.
+]=]
+ffi.u16 = {} :: u16
+export type u16 = NumCType<"u16">
+--[=[
+	@prop u32 NumCType
+	@within FFI
+
+	A 32-bit sized unsigned integer, Equivalent to `uint32_t` in `stdint`.
+]=]
+ffi.u32 = {} :: u32
+export type u32 = NumCType<"u32">
+--[=[
+	@prop u64 NumCType
+	@within FFI
+
+	A 64-bit sized unsigned integer, Equivalent to `uint64_t` in `stdint`.
+]=]
+ffi.u64 = {} :: u64
+export type u64 = NumCType<"u64">
+--[=[
+	@prop u128 NumCType
+	@within FFI
+
+	A 128-bit sized unsigned integer, Equivalent to `uint128_t` in `stdint`.
+]=]
+ffi.u128 = {} :: u128
+export type u128 = NumCType<"u128">
+--[=[
+	@prop i8 NumCType
+	@within FFI
+
+	A 8-bit sized signed integer, Equivalent to `int8_t` in `stdint`.
+]=]
+ffi.i8 = {} :: i8
+export type i8 = NumCType<"i8">
+--[=[
+	@prop i16 NumCType
+	@within FFI
+
+	A 16-bit sized signed integer, Equivalent to `int16_t` in `stdint`.
+]=]
+ffi.i16 = {} :: i16
+export type i16 = NumCType<"i16">
+--[=[
+	@prop i32 NumCType
+	@within FFI
+
+	A 32-bit sized signed integer, Equivalent to `int32_t` in `stdint`.
+]=]
+ffi.i32 = {} :: i32
+export type i32 = NumCType<"i32">
+--[=[
+	@prop i64 NumCType
+	@within FFI
+
+	A 64-bit sized signed integer, Equivalent to `int64_t` in `stdint`.
+]=]
+ffi.i64 = {} :: i64
+export type i64 = NumCType<"i64">
+--[=[
+	@prop i128 NumCType
+	@within FFI
+
+	A 128-bit sized signed integer, Equivalent to `int128_t` in `stdint`.
+]=]
+ffi.i128 = {} :: i128
+export type i128 = NumCType<"i128">
+--[=[
+	@prop f32 NumCType
+	@within FFI
+
+	A single-precision 32-bit sized floating-point, Almost always equivalent to `float` in C.
+]=]
+ffi.f32 = {} :: f32
+export type f32 = NumCType<"f32">
+--[=[
+	@prop f64 NumCType
+	@within FFI
+
+	A double-precision 64-bit sized floating-point, Almost always equivalent to `double` in C.
+]=]
+ffi.f64 = {} :: f64
+export type f64 = NumCType<"f64">
+--[=[
+	@prop usize NumCType
+	@within FFI
+
+	A machine specific pointer sized unsigned integer.
+]=]
+ffi.usize = {} :: usize
+export type usize = NumCType<"usize">
+--[=[
+	@prop isize NumCType
+	@within FFI
+
+	A machine specific pointer sized signed integer.
+]=]
+ffi.isize = {} :: isize
+export type isize = NumCType<"isize">
+--#endregion Fixed size Rust-style types
+
+--#region Variable size C-style types
+--[=[
+	@prop char NumCType
+	@within C
+
+	Compiler defined C `char` type.
+
+	The signedness may differ depending on the compiler and platform.
+
+	You can get signedness by `signedness` field.
+]=]
+c.char = {} :: char
+export type char = NumCType<"char">
+--[=[
+	@prop uchar NumCType
+	@within C
+
+	Compiler defined C `unsigned char` type.
+
+	Mostly equivalent to `u8`.
+]=]
+c.uchar = {} :: uchar
+export type uchar = NumCType<"uchar">
+--[=[
+	@prop schar NumCType
+	@within C
+
+	Compiler defined C `signed char` type.
+]=]
+c.schar = {} :: schar
+export type schar = NumCType<"schar">
+--[=[
+	@prop short NumCType
+	@within C
+
+	Compiler defined C `short` type.
+]=]
+c.short = {} :: short
+export type short = NumCType<"short">
+--[=[
+	@prop ushort NumCType
+	@within C
+
+	Compiler defined C `unsigned short` type.
+]=]
+c.ushort = {} :: ushort
+export type ushort = NumCType<"ushort">
+--[=[
+	@prop int NumCType
+	@within C
+
+	Compiler defined C `int` type.
+
+	The side may differ depending on the compiler and platform.
+]=]
+c.int = {} :: int
+export type int = NumCType<"int">
+--[=[
+	@prop uint NumCType
+	@within C
+
+	Compiler defined C `unsigned int` type.
+
+	The side may differ depending on the compiler and platform.
+]=]
+c.uint = {} :: uint
+export type uint = NumCType<"uint">
+--[=[
+	@prop long NumCType
+	@within C
+
+	Compiler defined C `long` type.
+
+	The side may differ depending on the compiler and platform.
+]=]
+c.long = {} :: long
+export type long = NumCType<"long">
+--[=[
+	@prop ulong NumCType
+	@within C
+
+	Compiler defined C `unsigned long` type.
+
+	The side may differ depending on the compiler and platform.
+]=]
+c.ulong = {} :: ulong
+export type ulong = NumCType<"ulong">
+--[=[
+	@prop longlong NumCType
+	@within C
+
+	Compiler defined C `unsigned longlong` type.
+]=]
+c.longlong = {} :: longlong
+export type longlong = NumCType<"longlong">
+--[=[
+	@prop longlong NumCType
+	@within C
+
+	Compiler defined C `unsigned longlong` type.
+]=]
+c.ulonglong = {} :: ulonglong
+export type ulonglong = NumCType<"ulonglong">
+--#endregion Variable size C-style types
+
+--[=[
+	@class CTypes
+
+	All possible C types.
+]=]
+export type CTypes =
+	u8
+	| u16
+	| u32
+	| u64
+	| u128
+	| i8
+	| i16
+	| i32
+	| i64
+	| i128
+	| f32
+	| f64
+	| usize
+	| isize
+	| char
+	| uchar
+	| schar
+	| short
+	| ushort
+	| int
+	| uint
+	| long
+	| ulong
+	| longlong
+	| ulonglong
+	| CArrInfo<CTypes, any>
+	| CPtrInfo<CTypes>
+	| CFnInfo
+	| CStructInfo
+	| CVoidInfo
+
+--[=[
+	@within C
+
+	Create a function signature type information.
+
+	@param args An array of CTypes represents the arguments of the function
+	@param ret The return type of the function
+	@return A function signature type information
+]=]
+function c.fn(args: { CTypes }, ret: CTypes): CFnInfo
+	return nil :: any
+end
+
+--[=[
+	@within C
+
+	Create a struct type information.
+
+	@param fields An array of CTypes represents the fields of the struct
+	@return A struct type information
+]=]
+function c.struct(fields: { CTypes }): CStructInfo
+	return nil :: any
+end
+
+--[=[
+	@within FFI
+
+	Create a `Ref` with address 0.
+
+	Can be used for receive a pointer from external function or pass it as an argument.
+
+	@return A zero initialized Ref
+]=]
+function ffi.nullRef(): RefData
+	return nil :: any
+end
+
+--[=[
+	@within FFI
+
+	Create a `Box` with specific size.
+	The created box is not filed with zero.
+
+	@param size The size of the new box
+	@return A allocated box
+]=]
+function ffi.box(size: number): BoxData
+	return nil :: any
+end
+
+--[=[
+	@within FFI
+
+	Open a dynamic library.
+
+	@param name The name of the target library
+	@return A dynamic library handle
+]=]
+function ffi.open(name: string): LibData
+	return nil :: any
+end
+
+--[=[
+	@within FFI
+
+	Return `true` if the second argument is an integer (i32).
+
+	@param val A lua value to check
+	@return Whether val is an integer or not
+]=]
+function ffi.isInteger<T>(val: T): boolean
+	return nil :: any
+end
+
+--[=[
+	@within FFI
+
+	Free referenced memory.
+
+	@param data Target memory to free
+]=]
+function ffi.free(data: RefData | BoxData)
+	return nil :: any
+end
+
+return ffi