Automatic Rust and Lua glue code generation for seamless FFI interop
Clone or download
Pull request Compare This branch is even with distil:master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Type Name Latest commit message Commit time
Failed to load latest commit information.

Lua to Rust FFI code generation

Motivating example


pub struct A {
    string: String,
    integer: i32,

pub mod extern_ffi {
    pub fn make_a(string: &str, integer: i32) -> A {
        A {
            string: string.to_owned(),

    pub fn describe(a: A) -> String {
        format!("A: {:?}", a)


local example = require('rust-example')

local a = example.make_a("Test string", 42)
print("a", a.string, a.integer)

print("describe", example.describe(a))

Implementation details


  • Supported Rust types include primitives, Vec, Option, String and custom struct with derive(LuaMarshalling) and any combination of those. &str is supported only as an argument but is faster than String. &[] is supported only for primitive types. Result is supported only as a return argument.
  • Options None is nil in Lua.
  • Only &str and &[] of primitive types are passed as references to Rust, all other types are copied.
  • A Rust struct is converted to a Lua table, but can still be used as an argument. For this to work, the Lua table also keeps a reference to the native object pointer.
  • The native object pointer is garbage collected by calling back to Rust. To keep the native pointer and the table consistent, the table is immutable.

panic and error

  • Passing a Lua string to Rust as &str or String may fail with an error due to UTF-8 requirements. However, passing a Lua string to Rust as a &[u8] or Vec<u8> will not.
  • Returning a string from Rust may fail as well with an error due to strings containing the zero-byte.
  • A Rust panic will cause an error in Lua.

Known Issues

  • Vec<Option<T>> have been disabled. Lua arrays generally do not handle null values well. See for more information.
  • struct typenames must be unique. Separate modules are not enough.
  • Identifiers can not be Lua or C reserved keywords. For example, a variable cannot be called short.
  • The __ prefix is reserved for hidden identifiers and should not be used as field names or function arguments.



cargo new example_setup
  • In example_setup create the file src/ with the following content
extern crate generator;

use std::env;

fn main() {
    let rust_output = ::std::path::Path::new(&env::var("OUT_DIR").unwrap()).join("");

    let output = generator::generate(
        &env::current_dir().unwrap().as_path().join("src/"), "example_setup");

    use std::io::Write;


Note the library_name parameter to generator::generator must be equal to the library name of the crate.

Add the following to the Cargo.toml under [package]

build = "src/"

Under [dependencies] add the following

libc = "0.2.20"
c-marshalling = { git = "" }
lua-marshalling = { git = "" }

Add the following section to the Cargo.toml as well

generator = { git = "" }

crate-type = ["cdylib"]

In src/ add the following

extern crate libc;
extern crate lua_marshalling;
extern crate c_marshalling;

pub mod extern_ffi {
    pub fn hello_world() -> String {
        "Hello World!".to_owned()

include!(concat!(env!("OUT_DIR"), "/"));


After the library has been built, the Lua interface code can be generated using the following command

LD_LIBRARY_PATH=..path-to-example_setup/target/debug/ \
    luajit ..path-to-rust_lua_ffi/lua/bootstrap.lua example_setup > api.lua


To use the api.lua file generated in the Building step, create a Lua file called example.lua in the same directory as the Lua interface code containing

local api = require('api')


Execute the file using the following command

LD_LIBRARY_PATH=..path-to-example_setup/target/debug/ luajit example.lua