Rust library to interface with Lua

Overview

hlua

This library is a high-level binding for Lua 5.2. You don't have access to the Lua stack, all you can do is read/write variables (including callbacks) and execute Lua code.

Build Status

How to install it?

Add this to the Cargo.toml file of your project

[dependencies]
hlua = "0.3"

How to use it?

extern crate hlua;
use hlua::Lua;

The Lua struct is the main element of this library. It represents a context in which you can execute Lua code.

let mut lua = Lua::new();     // mutable is mandatory

You can check the documentation here.

Reading and writing variables

lua.set("x", 2);
lua.execute::<()>("x = x + 1").unwrap();
let x: i32 = lua.get("x").unwrap();  // x is equal to 3

Reading and writing global variables of the Lua context can be done with set and get. The get function returns an Option<T> and does a copy of the value.

The base types that can be read and written are: i8, i16, i32, u8, u16, u32, f32, f64, bool, String. &str can be written but not read.

If you wish so, you can also add other types by implementing the Push and LuaRead traits.

Executing Lua

let x: u32 = lua.execute("return 6 * 2;").unwrap();    // equals 12

The execute function takes a &str and returns a Result<T, ExecutionError> where T: LuaRead.

You can also call execute_from_reader which takes a std::io::Read as parameter. For example you can easily execute the content of a file like this:

lua.execute_from_reader::<()>(File::open(&Path::new("script.lua")).unwrap())

Writing functions

In order to write a function, you must wrap it around hlua::functionX where X is the number of parameters. This is for the moment a limitation of Rust's inferrence system.

fn add(a: i32, b: i32) -> i32 {
    a + b
}

lua.set("add", hlua::function2(add));
lua.execute::<()>("local c = add(2, 4)");   // calls the `add` function above
let c: i32 = lua.get("c").unwrap();   // returns 6

In Lua, functions are exactly like regular variables.

You can write regular functions as well as closures:

lua.set("mul", hlua::function2(|a: i32, b: i32| a * b));

Note that the lifetime of the Lua context must be equal to or shorter than the lifetime of closures. This is enforced at compile-time.

let mut a = 5i;

{
    let mut lua = Lua::new();

    lua.set("inc", || a += 1);    // borrows 'a'
    for i in (0 .. 15) {
        lua.execute::<()>("inc()").unwrap();
    }
} // unborrows `a`

assert_eq!(a, 20)
Error handling

If your Rust function returns a Result object which contains an error, then a Lua error will be triggered.

Manipulating Lua tables

Manipulating a Lua table can be done by reading a LuaTable object. This can be achieved easily by reading a LuaTable object.

let mut table: hlua::LuaTable<_> = lua.get("a").unwrap();

You can then iterate through the table with the .iter() function. Note that the value returned by the iterator is an Option<(Key, Value)>, the Option being empty when either the key or the value is not convertible to the requested type. The filter_map function (provided by the standard Iterator trait) is very useful when dealing with this.

for (key, value) in table.iter().filter_map(|e| e) {
    ...
}

You can also retreive and modify individual indices:

let x = table.get("a").unwrap();
table.set("b", "hello");

Calling Lua functions

You can call Lua functions by reading a functions_read::LuaFunction.

lua.execute::<()>("
    function get_five() 
        return 5
    end");

let get_five: hlua::LuaFunction<_> = lua.get("get_five").unwrap();
let value: i32 = get_five.call().unwrap();
assert_eq!(value, 5);

This object holds a mutable reference of Lua, so you can't read or modify anything in the Lua context while the get_five variable exists. It is not possible to store the function for the moment, but it may be in the future.

Reading and writing Rust containers

(note: not yet possible to read all containers, see below)

It is possible to read and write whole Rust containers at once:

lua.set("a", [ 12, 13, 14, 15 ]);
let hashmap: HashMap<i32, f64> = [1., 2., 3.].into_iter().enumerate().map(|(k, v)| (k as i32, *v as f64)).collect();
lua.set("v", hashmap);

If the container has single elements, then the indices will be numerical. For example in the code above, the 12 will be at index 1, the 13 at index 2, etc.

If the container has tuples of two elements, then the first one will be considered as the key and the second one as the value.

This can be useful to create APIs:

fn foo() { }
fn bar() { }

lua.set("mylib", [
    ("foo", hlua::function0(foo)),
    ("bar", hlua::function0(bar))
]);

lua.execute::<()>("mylib.foo()");

It is possible to read a Vec<AnyLuaValue>:

        let mut lua = Lua::new();

        lua.execute::<()>(r#"v = { 1, 2, 3 }"#).unwrap();

        let read: Vec<_> = lua.get("v").unwrap();
        assert_eq!(
            read,
            [1., 2., 3.].iter()
                .map(|x| AnyLuaValue::LuaNumber(*x)).collect::<Vec<_>>());

In case table represents sparse array, has non-numeric keys, or indices not starting at 1, .get() will return None, as Rust's Vec doesn't support these features.

It is possible to read a HashMap<AnyHashableLuaValue, AnyLuaValue>:

let mut lua = Lua::new();

lua.execute::<()>(r#"v = { [-1] = -1, ["foo"] = 2, [2.] = 42 }"#).unwrap();

let read: HashMap<_, _> = lua.get("v").unwrap();
assert_eq!(read[&AnyHashableLuaValue::LuaNumber(-1)], AnyLuaValue::LuaNumber(-1.));
assert_eq!(read[&AnyHashableLuaValue::LuaString("foo".to_owned())], AnyLuaValue::LuaNumber(2.));
assert_eq!(read[&AnyHashableLuaValue::LuaNumber(2)], AnyLuaValue::LuaNumber(42.));
assert_eq!(read.len(), 3);

User data

(note: the API here is very unstable for the moment)

When you expose functions to Lua, you may wish to read or write more elaborate objects. This is called a user data.

To do so, you should implement the Push, CopyRead and ConsumeRead for your types. This is usually done by redirecting the call to userdata::push_userdata.

struct Foo;

impl<L> hlua::Push<L> for Foo where L: hlua::AsMutLua<'lua> {
    fn push_to_lua(self, lua: L) -> hlua::PushGuard<L> {
        lua::userdata::push_userdata(self, lua,
            |mut metatable| {
                // you can define all the member functions of Foo here
                // see the official Lua documentation for metatables
                metatable.set("__call", hlua::function0(|| println!("hello from foo")))
            })
    }
}

fn main() {
    let mut lua = lua::Lua::new();
    lua.set("foo", Foo);
    lua.execute::<()>("foo()");       // prints "hello from foo"
}

Creating a Lua module

Note: OBSOLETE ; this is still some pre-Rust-1.0 stuff

This library also includes a second library named rust-hl-lua-module which allows you to create Lua modules in Rust.

To use it, add this to Cargo.toml:

[dependencies.rust-hl-lua-modules]
git = "https://github.com/tomaka/hlua"

Then you can use it like this:

#![feature(phase)]
#[!plugin(rust-hl-lua-modules)]

#[export_lua_module]
pub mod mylib {         // <-- must be the name of the Lua module
    static PI: f32 = 3.141592;

    fn function1(a: int, b: int) -> int {
        a + b
    }

    fn function2(a: int) -> int {
        a + 5
    }

    #[lua_module_init]
    fn init() {
        println!("module initialized!")
    }
}

This module will then be usable by Lua:

> mylib = require("mylib")
module initialized!
> return mylib.function1(2, 4)
6
> return mylib.PI
3.141592

Two syntax extensions are defined:

  • #[export_lua_module]: Must be put in front of a module. The name of the module must be the same as the name of your Lua module.
  • #[lua_module_init]: Can be put in front of a function inside the module. This function will be executed when the module is loaded.

Restrictions:

  • fail!() will crash the program.
  • If you spawn tasks, they will have to end before the hand is given back to lua.

Contributing

Contributions are welcome!

Comments
  • AnyLuaValue::LuaArray still can't be used in Lua

    AnyLuaValue::LuaArray still can't be used in Lua

    I have a program that uses AnyLuaValues a lot. We have a data store that the Rust and Lua parts share back and forth across, and the easiest way to share data back and forth was through AnyLuaValues.

    I'd written traits ToTable and FromTable for the conversions (using move semantics to avoid copies):

        #[derive(Debug, Clone, PartialEq, Eq, Hash)]
        struct Point {
            x: i32,
            y: u32
        }
        impl ToTable for Point {
            fn to_table(self) -> AnyLuaValue {
                LuaArray(vec![
                    (LuaString("x".into()), LuaNumber(self.x as f64)),
                    (LuaString("y".into()), LuaNumber(self.y as f64))
                ])
            }
        }
        impl FromTable for Point {
            fn from_table(decoder: LuaDecoder) -> ConvertResult<Point> {
                let (decoder, x) = try!(decoder.read_field("x".into()));
                let (_decoder, y) = try!(decoder.read_field("y".into()));
                Ok(Point { x: x, y: y })
            }
        }
    

    This is also a great way to store structured data (like a slimmed-down JSON) and it made it easy to provide compatibility without using userdata, &mut Lua or PushGuard<L> or AsMutLua. I've even got a macro to generate a struct with these implementations automatically.

    However, pushing and reading AnyLuaData::LuaArray is unsupported right now, with a little comment that mentions hitting a stack limit. Is there a way to implement it?

    opened by SnirkImmington 9
  • Callable fails to build - Callable trait not implemented

    Callable fails to build - Callable trait not implemented

    The call to load_args_and_call at line 20 of functions_write.rs causes the cargo build to fail.

    functions_write.rs:20:5: 20:33 error: the trait `functions_write::Callable<'_,<generic #81>,<generic #82>>` is not implemented for the type `T`
    
    functions_write.rs:20     data.load_args_and_call(lua)
    
    bug 
    opened by cleversoap 7
  • rust-hl-modules fails with file operations?

    rust-hl-modules fails with file operations?

    Hi, I'm starting to experiment your fantastic library, especially to create lib for Lua. One uses the rust-image lib: it's ok if the code don't access to the file system otherwise the process crashed (with the same error reported below). Another experiment is an attempt with rust-tabular... but fails. So I began to think that I/O is the problem and I write this example: saving a text file from Lua perfectly functional in "only Rust" code.

    #![feature(phase)]
    
    #[phase(plugin)]
    extern crate lua_mod = "rust-hl-lua-modules";
    
    #[export_lua_module]
    pub mod save {// <-- must be the name of the Lua module
        use std::io::File;
        fn savefile() {
            let p = Path::new("a-file.txt");
            let mut file = match File::create(&p) {
                Ok(f) => f,
                Err(e) => fail!("file error: {}", e),
            };
            file.write(b"content");
        }
    
        #[lua_module_init]
        fn init() {
            println!("module initialized!")
        }
    }
    

    Then I compile it with Cargo with this config:

    [package]
    
    name = "save"
    version = "0.1.0"
    authors = [ "[email protected]" ]
    
    [[lib]]
    
    name = "save" # the name of the executable to generate
    crate_type = ["dylib"]
    
    [dependencies.rust-hl-lua-modules]
    
    git = "https://github.com/tomaka/rust-hl-lua"
    

    In a terminal session the behaviour of the library is this:

    roberto@roberto-desktop:~/Scrivania/test-save/test$ lua
    Lua 5.2.3  Copyright (C) 1994-2013 Lua.org, PUC-Rio
    > s = require "save"
    module initialized!
    > s.savefile()
    
    You've met with a terrible fate, haven't you?
    
    fatal runtime error: Could not unwind stack, error = 5
    Istruzione non consentita (core dump creato)
    

    Can you help me? Is a question of local library installed on my system? Thank you very much. My dev platform is an Ubuntu 14.04 with ultimate version of Rust (0.12.0-pre-nightly 25741603f), Cargo and github library.

    bug 
    opened by robitex 7
  • Made Lua.get return None if the value is nil, even if the type being …

    Made Lua.get return None if the value is nil, even if the type being …

    …casted to has a value for nil

    This change is slightly more breaking, since it changes behaviour instead of adding.

    This is the second concern in #66.

    opened by Ameliorate 5
  • Why does implement_lua_read!(...) not implement everything you need?

    Why does implement_lua_read!(...) not implement everything you need?

    If you want to have functions that take 2 or more parameters with one of them being a user data type, it won't work as the impls for it are missing, so you need to write additional impls: http://puu.sh/oSHM3.png

    This seems weird. Am I missing something or why is this the case?

    Looks like this doesn't even work, so I don't know what to do now :/

    This is the error I'm getting: http://puu.sh/oSInR.png

    opened by CryZe 4
  • Implement reading arrays into AnyLuaValue and AnyHashableLuaValue

    Implement reading arrays into AnyLuaValue and AnyHashableLuaValue

    This, as far as I can tell, solves the issue of recursive &mut crashing the compiler in the same way that it was solved in Push.

    Tests are a bit hairy but I can't think of a cleaner way to write them at the moment.

    opened by tpdickso 3
  • New release?

    New release?

    Hi -- are there any plans to do a new release any time soon? From a quick eyeball it looks like there are a bunch of incompatibilities, so I guess the release will be 0.4.0.

    opened by sunshowers 3
  • Passing non-UTF-8 strings between Lua and Rust is impossible

    Passing non-UTF-8 strings between Lua and Rust is impossible

    Lua strings are bytestrings, and the language doesn't prescribe any particular encoding for them (Lua 5.3 adds \u{} escapes which expand to UTF-8 code units, but this is a purely syntactic convenience). This binding, however, expects all strings to be UTF-8 and doesn't allow passing or receiving arbitrary byte sequences between Rust code and Lua code.

    Furthermore, this imposition of arbitrary semantics was done in a particularly sloppy way: the code below will panic instead of just returning an Err(_) value (which happens for e.g. mismatched types).

    extern crate hlua;
    
    fn main() {
        let mut lua = hlua::Lua::new();
        let result = lua.execute::<String>("return '\\255'");
        println!("{:?}", result);
    }
    
    opened by fstirlitz 3
  • Accept LuaTable as function argument?

    Accept LuaTable as function argument?

    This code, when added as test to lua_tables.rs, fails to compile:

        #[test]
        fn accepting_luatable_as_fn_arg_works() {
            let mut lua = Lua::new();
            let foo = {
                move |config: LuaTable<_>| for (k, v) in
                    config.iter::<String, String>().filter_map(|e| e)
                {
                    println!("{} => {}", k, v);
                }
            };
            lua.set("foo", ::function1(foo));
        }
    

    with this error:

       Compiling hlua v0.3.1 (file:///home/mkpankov/workspace/projects/hlua/hlua)
    error[E0277]: the trait bound `for<'p> lua_tables::LuaTable<_>: LuaRead<&'p mut functions_write::InsideCallback>` is not satisfied
       --> src/lua_tables.rs:668:13
        |
    668 |         lua.set("foo", ::function1(foo));
        |             ^^^ the trait `for<'p> LuaRead<&'p mut functions_write::InsideCallback>` is not implemented for `lua_tables::LuaTable<_>`
        |
        = help: the following implementations were found:
                  <lua_tables::LuaTable<L> as LuaRead<L>>
        = note: required because of the requirements on the impl of `for<'p> LuaRead<&'p mut functions_write::InsideCallback>` for `(lua_tables::LuaTable<_>,)`
        = note: required because of the requirements on the impl of `for<'a> PushOne<&'a mut Lua<'_>>` for `functions_write::Function<[closure@src/lua_tables.rs:662:13: 666:14], (lua_tables::LuaTable<_>,), ()>`
    

    Is it possible at all?

    opened by mkpankov 3
  • Tables can be in any argument position for Rust functions

    Tables can be in any argument position for Rust functions

    Tables now have an index field, which keeps track of where they are in the lua stack, and the lua FFI functions are now offset relative to this index (when relevant.)

    This is my first pull request on Github, and I know only as much about the Lua C API as I skimmed in the past hour or so, but I ran this through a small gauntlet of tests with Rust FFI functions accepting multiple user-data types that are pushed to and read from Lua as tables, and it seems to work. (My data types are all just vectors and matrices and such, though, so no nested tables. It may be worth giving a try with those if you have some on hand.)

    Thanks!

    opened by tpdickso 3
  • Fix hundreds of improper_ctypes warnings

    Fix hundreds of improper_ctypes warnings

    This does it by adding a 8 bit padding value to the lua_State struct.

    These warnings, however, are still correct. The C spec does not account for zero sized structs. In C, structs that are empty still take up space, for the reason that two different variables will always have different addresses.

    opened by Ameliorate 3
  • Panics on executing malformed lua

    Panics on executing malformed lua

    let mut lua = hlua::Lua::new();
    match lua.execute::<()>("return 99k") {
       _ => ()
    }
    

    panics: thread 'main' panicked at 'attempted to leave type `&mut hlua::Lua<'_>` uninitialized, which is invalid', [...]\hlua-0.4.1\src\lib.rs:230:19

    opened by JustMog 0
  • Heads-up: UB due to misuse of mem::uninitialized will soon lead to panic

    Heads-up: UB due to misuse of mem::uninitialized will soon lead to panic

    Here, this crate causes UB by "Producing an invalid value". Concretely, it produces a value of an arbitrary type L with mem::uninitialized(). In the near future the call to mem::uninitialized() will panic to avoid UB, and our crater run determined that this crate will be affected.

    mem::uninitialized() is deprecated since Rust 1.39. The intended replacement is MaybeUninit, which tracks the possibility of uninitialized values at the type level to make sure the compiler does not make any false assumptions.

    opened by RalfJung 0
  • Standard libraries can not be imported

    Standard libraries can not be imported

    I think this is addressed in #147, basically calling lua.open_math(); doesn't do anything since the math object is made available.

    This is also the case for eg. strings, but s:match(...) still works, while string.match(s, ...) doesn't.

    opened by kpcyrd 0
  • Forking a library and potential maintainer list

    Forking a library and potential maintainer list

    Hello @tomaka and everyone else,

    The following is not to bash the author and previous contributors, just statements of facts.

    At this point it's pretty clear library is unmaintained, and issues and PRs just pile up.

    No major PR was accepted for half a year at least, with several having no comments and otherwise being stale.

    I propose forking a library to an organization, and establishing a small team of currently active library users.

    We'd then take care of current unattended PRs and at least triage the issues.

    In case there are any objections, please let us know.

    Tagging people who might be interested in maintaining, based on previous contributions:

    • @mkpankov
    • @tpdickso
    • @giodamelio
    • @kpcyrd
    • @linkmauve
    • @sunshowers
    • @jonas-schievink
    • @Ameliorate
    • @TyOverby

    Let's discuss the path forward.

    opened by mkpankov 8
Owner
Pierre Krieger
Pierre Krieger
πŸ±β€πŸ‘€ Cross-language static library for accessing the Lua state in Garry's Mod server plugins

gmserverplugin This is a utility library for making Server Plugins that access the Lua state in Garry's Mod. Currently, accessing the Lua state from a

William 5 Feb 7, 2022
Lua 5.3 bindings for Rust

rust-lua53 Aims to be complete Rust bindings for Lua 5.3 and beyond. Currently, master is tracking Lua 5.3.3. Requires a Unix-like environment. On Win

J.C. Moyer 150 Dec 14, 2022
Safe Rust bindings to Lua 5.1

rust-lua Copyright 2014 Lily Ballard Description This is a set of Rust bindings to Lua 5.1. The goal is to provide a (relatively) safe interface to Lu

Lily Ballard 124 Jan 5, 2023
Zero-cost high-level lua 5.3 wrapper for Rust

td_rlua This library is a high-level binding for Lua 5.3. You don't have access to the Lua stack, all you can do is read/write variables (including ca

null 47 May 4, 2022
Pure Rust Lua implementation

purua Pure Rust Lua implementation Usage $ bat lua_examples/defun.lua ───────┬────────────────────────────────────────── β”‚ File: lua_examples/d

Kondo Uchio 35 Dec 28, 2021
A script language like Python or Lua written in Rust, with exactly the same syntax as Go's.

A script language like Python or Lua written in Rust, with exactly the same syntax as Go's.

null 1.4k Jan 1, 2023
A parser, compiler, and virtual machine evaluator for a minimal subset of Lua; written from scratch in Rust.

lust: Lua in Rust This project implements a parser, compiler, and virtual machine evaluator for a minimal subset of Lua. It is written from scratch in

Phil Eaton 146 Dec 16, 2022
Rust scaffold system with Lua embedded applets.

brickpack-2022 Demo Powered by Github Actions CI/CD (Heroku) https://demo-1642622230.herokuapp.com/#/users Frontent Runner Rendered sample code (Lua 5

null 0 Nov 24, 2022
⚑ Fast Web Security Scanner written in Rust based on Lua Scripts πŸŒ– πŸ¦€

⚑ Fast Web Security Scanner written in Rust based on Lua Scripts ?? ??

Rusty Sec 14 Nov 28, 2022
πŸ“¦ Pack hundreds of Garry's Mod Lua files into just a handful

?? gluapack gluapack is a program that can pack hundreds of Garry's Mod Lua files into just a handful. Features Quick, easy and portable - perfect for

null 11 Aug 10, 2021
A memory safe Lua interpreter

Hematita Da Lua Hematita Da Lua is an interpreter for the scripting language Lua, written entirely in 100% safe Rust. Hematita is the portugese word f

Daniel 149 Dec 29, 2022
A super-lightweight Lua microservice (toy) framework.

Hive A super-lightweight microservice (toy) framework written in Rust. It uses Lua as interface to provide simple, fun developing experience and fast

Eric Long 39 Aug 14, 2022
This tool converts Lua code to TS automatically, including the conversion of common standards to their TS equivalents.

lua-to-ts This tool converts Lua code to TS automatically, including the conversion of common standards to their TS equivalents. Code that fails to be

Dion 11 Nov 21, 2022
Another cursed Garry's Mod module. This time, it adds the C preprocessor to Lua scripts

gm_cpreprocessor Another cursed Garry's Mod module. This time, it adds the C preprocessor to Lua scripts. It works by detouring RunStringEx and overri

William 6 Aug 14, 2022
Node.js bindings to Lua

Node.js bindings to Lua

Connor Brewster 6 Dec 19, 2022
Rust bindings to the Java Native Interface β€” JNI

JNI Bindings for Rust This project provides complete JNI bindings for Rust, allowing to: Implement native Java methods for JVM and Android in Rust Cal

null 768 Dec 30, 2022
Deno Foreign Function Interface.

deno_plugin_ffi (WIP & Need Help) Deno Foreign Function Interface. deno_ffi is a Deno plugin for loading and calling dynamic libraries using pure Java

Deno Foreign Function Interface 37 Aug 18, 2022
CARBON is an interface-centric programming language named after the concept of an allotropy.

CARBON programming language Status: just an idea CARBON is an interface-centric programming language named after the concept of an allotropy. It is an

Tim McNamara 4 Aug 18, 2022
Rust library for build scripts to compile C/C++ code into a Rust library

A library to compile C/C++/assembly into a Rust library/application.

Alex Crichton 1.3k Dec 21, 2022