If malloc actually returned a struct, the __gc metamethod of the corresponding type would not be called because you never "created" the instance (malloc did), and the __gc function is only used for an "implicit ffi.gc call during creation of an instance". This makes sense, because objects created outside of ffi.new usually need custom GC behavior anyway (malloc'd pointers need freeing, objects obtained from some C-API usually need to be passed to a special free-function from said API, etc.). Blindly invoking __gc functions for structs obtained from C functions would probably be disastrous more often than not. Note that passing/returning structs by value currently prevents JIT compilation of surrounding code and is seldomly seen in C-APIs anyway. If you want to use malloc instead of ffi.new, it is IMHO best to keep your object as pointer, because you *need* the pointer when free'ing the object, and there is no "ffi.addressof" (by design). Dereferencing mallocs return value immediately is just not worth it IMO. ---------------------------------------------------------------------------- -- Here is a simple malloc example that shows how LuaJIT deals with -- pointers-to-structs vs structs, and what the (few) differences in -- behavior are: ---------------------------------------------------------------------------- local ffi = require'ffi' ffi.cdef[[ void * malloc(size_t bytes); void free(void *ptr); ]] local typeof, sizeof = ffi.typeof, ffi.sizeof local malloc do local libc, fill, cast, gc = ffi.C, ffi.fill, ffi.cast, ffi.gc malloc = function(ctype) local ptr = cast(typeof('$ *', ctype), libc.malloc(sizeof(ctype))) fill(ptr, sizeof(ctype)) --zero-fill gc(ptr, libc.free) return ptr end end local PointF = typeof'struct {double x; double y;}' do local sqrt = math.sqrt local mt = { __index = { distance = function(a, b) return sqrt((a.x-b.x)^2 + (a.y-b.y)^2) end, }, } ffi.metatype(PointF, mt) end local p = PointF(1, 1) local p2 = malloc(PointF) print(p:distance{x=2, y=2}) -- LuaJIT dereferences the pointer for us. -- C/C++ would force us to write "p2->distance" here. print(p2:distance{x=2, y=2}) print(sizeof(p), sizeof(p2)) --> 16, <pointer size> -- In C/C++, one would need to write "p2->x" print(p.x, p2.x) --> 1, 0 -- dereferencing before accessing the member only works with the pointer print(p2[0].x) --> 0