Examples and initial README

This commit is contained in:
kyren 2017-05-21 21:47:32 -04:00
parent 065c69894a
commit c7693ae0a2
2 changed files with 202 additions and 0 deletions

68
README.md Normal file
View file

@ -0,0 +1,68 @@
# rlua -- High level bindings between Rust and Lua
This library is a WIP high level interface between Rust and Lua. Its major goal
is to expose as flexible of an API between Rust and Lua as possible, while also
being completely safe.
There are other high level lua bindings systems for rust, and this crate is an
exploration of a different part of the design space. The main high level
interface to Lua right now is [hlua](https://github.com/tomaka/hlua/) which you
should definitely check out and use if it suits your needs. This crate has the
following differences with hlua:
* Handles to Lua values use the Lua registry, not the stack
* Handles to Lua values are all internally mutable
* Handles to Lua values use non-mutable borrows the main Lua object, so
there can be multiple handles or long lived handles
* Targets lua 5.3
The key difference here is that rlua handles rust-side references to Lua values
in a fundamentally different way than hlua, more similar to other lua bindings
systems like (Selene)[https://github.com/jeremyong/Selene] for C++. Values like
LuaTable and LuaFunction that hold onto Lua values in the Rust stack, instead of
pointing at values in the Lua stack, are placed into the registry with luaL_ref.
In this way, it is possible to have an arbitrary number of handles to internal
Lua values at any time, created and destroyed in arbitrary order. This approach
IS slightly slower than the approach that hlua takes of only manipulating the
lua stack, but this, combined with internal mutability, allows for a much more
flexible API.
Currently exposes a *somewhat* complete Lua API covering values and tables and
functions and userdata, but does not yet cover coroutines. This API is actually
heavily inspired by the lua API that I previously wrote for Starbound, and will
become feature complete with that API over time. Some capabilities that API has
that are on the roadmap:
* Proper coroutine support
* Lua profiling support
* Execution limits like total instruction limits or lua <-> rust recursion
limits
* Security limits on the lua stdlib, and general control over the loaded
lua libraries.
* "Context" or "Sandboxing" support, this was probably a bit too heavyweight
in Starbound's API, but there will be the ability to set the _ENV upvalue
of a loaded chunk to a table other than _G, so that you can have different
environments for different loaded chunks.
There are also some more general things that need to be done:
* More fleshed out Lua API, things like Table metatables and exposing the
registry.
* MUCH better API documentation, the current API documentation is basically
non-existent.
* Performance testing.
Additionally, there are ways I would like to change this API, once support lands
in rustc. For example:
* Once ATCs land, there should be a way to wrap callbacks based on argument
and return signature, rather than calling lua.pack / lua.unpack inside the
callback. Until then, it is impossible to name the type of the function
that would do the wrapping, see (this reddit
discussion)[https://www.reddit.com/r/rust/comments/5yujt6/a_very_complex_lifetime_problem_possibly_a/]
* Once tuple based variadic generics land, the plan is to completely
eliminate the lua multi macros in favor of simple tuples.
## Examples
Please look at the (examples)[examples/examples.rs]

134
examples/examples.rs Normal file
View file

@ -0,0 +1,134 @@
#[macro_use]
extern crate rlua;
use rlua::*;
fn examples() -> LuaResult<()> {
// Create a Lua context with Lua::new(). Eventually, this will allow further control on the
// lua std library, and will specifically allow limiting Lua to a subset of "safe"
// functionality.
let lua = Lua::new();
// You can get and set global variables
lua.set("string_var", "hello")?;
lua.set("int_var", 42)?;
assert_eq!(lua.get::<_, String>("string_var")?, "hello");
assert_eq!(lua.get::<_, i64>("int_var")?, 42);
// You can load and evaluate lua code. The second parameter here gives the chunk a better name
// when lua error messages are printed.
lua.load(r#"
global = 'foo'..'bar'
"#,
Some("example code"))?;
assert_eq!(lua.get::<_, String>("global")?, "foobar");
assert_eq!(lua.eval::<i32>("1 + 1")?, 2);
assert_eq!(lua.eval::<bool>("false == false")?, true);
assert_eq!(lua.eval::<i32>("return 1 + 2")?, 3);
// You can create and manage lua tables
let array_table = lua.create_empty_table()?;
array_table.set(1, "one")?;
array_table.set(2, "two")?;
array_table.set(3, "three")?;
assert_eq!(array_table.length()?, 3);
let map_table = lua.create_empty_table()?;
map_table.set("one", 1)?;
map_table.set("two", 2)?;
map_table.set("three", 3)?;
let v: i64 = map_table.get("two")?;
assert_eq!(v, 2);
// You can pass values like LuaTable back into Lua
lua.set("array_table", array_table)?;
lua.set("map_table", map_table)?;
lua.eval::<()>(r#"
for k, v in pairs(array_table) do
print(k, v)
end
for k, v in pairs(map_table) do
print(k, v)
end
"#)?;
// You can load lua functions
let print: LuaFunction = lua.get("print")?;
print.call::<_, ()>("hello from rust")?;
// There is a specific method for handling variadics that involves Heterogeneous Lists. This
// is one way to call a function with multiple parameters:
print
.call::<_, ()>(lua_multi!["hello", "again", "from", "rust"])?;
// You can bind rust functions to lua as well
let check_equal = lua.create_function(|lua, args| {
// Functions wrapped in lua receive their arguments packed together as LuaMultiValue. The
// first thing that most wrapped functions will do is "unpack" this LuaMultiValue into its
// parts. Due to lifetime type signature limitations, this cannot be done automatically from the
// function signature, but this will be fixed with ATCs. Notice the use of the hlist
// macros again.
let lua_multi_pat![list1, list2] = lua.unpack::<LuaMulti![Vec<String>, Vec<String>]>(args)?;
// This function just checks whether two string lists are equal, and in an inefficient way.
// Results are returned with lua.pack, which takes any number of values and turns them back
// into LuaMultiValue. In this way, multiple values can also be returned to Lua. Again,
// this cannot be inferred as part of the function signature due to the same lifetime type
// signature limitations.
lua.pack(list1 == list2)
})?;
lua.set("check_equal", check_equal)?;
assert_eq!(lua.eval::<bool>(r#"check_equal({"a", "b", "c"}, {"a", "b", "c"})"#)?,
true);
assert_eq!(lua.eval::<bool>(r#"check_equal({"a", "b", "c"}, {"d", "e", "f"})"#)?,
false);
// You can create userdata with methods and metamethods defined on them. Here's a more
// complete example that shows all of the features of this API together
#[derive(Copy, Clone)]
struct Vec2(f32, f32);
impl LuaUserDataType for Vec2 {
fn add_methods(methods: &mut LuaUserDataMethods<Self>) {
methods.add_method("magnitude", |lua, vec, _| {
let mag_squared = vec.0 * vec.0 + vec.1 * vec.1;
lua.pack(mag_squared.sqrt())
});
methods.add_meta_function(LuaMetaMethod::Add, |lua, params| {
let lua_multi_pat![vec1, vec2] = lua.unpack::<LuaMulti![Vec2, Vec2]>(params)?;
lua.pack(Vec2(vec1.0 + vec2.0, vec1.1 + vec2.1))
});
}
}
let vec2_constructor = lua.create_function(|lua, args| {
let lua_multi_pat![x, y] = lua.unpack::<LuaMulti![f32, f32]>(args)?;
lua.pack(Vec2(x, y))
})?;
lua.set("vec2", vec2_constructor)?;
assert_eq!(lua.eval::<f32>("(vec2(1, 2) + vec2(2, 2)):magnitude()")?,
5.0);
Ok(())
}
fn main() {
examples().unwrap();
}