mlua/examples/guided_tour.rs

213 lines
7.2 KiB
Rust
Raw Normal View History

use std::f32;
use std::iter::FromIterator;
use mlua::{chunk, Function, Lua, MetaMethod, Result, UserData, UserDataMethods, Variadic};
2017-05-21 20:47:32 -05:00
fn main() -> Result<()> {
// You can create a new Lua state with `Lua::new()`. This loads the default Lua std library
2019-10-17 10:59:33 -05:00
// *without* the debug library.
2017-05-21 20:47:32 -05:00
let lua = Lua::new();
// You can get and set global variables. Notice that the globals table here is a permanent
// reference to _G, and it is mutated behind the scenes as Lua code is loaded. This API is
2019-10-17 10:59:33 -05:00
// based heavily around sharing and internal mutation (just like Lua itself).
Another major API change, out of stack space is not an Err It, ahem "should not" be possible to exhaust lua stack space in normal usage, and causing stack errors to be Err is slightly obnoxious. I have been wanting to make this change for a while, and removing the callback API from tables makes this sensible *I think*. I can think of a couple of ways that this is not technically true, but I think that they are acceptable, or should be handled differently. One, you can make arbitrarily sized LuaVariadic values. I think this is maybe a bug already, because there is an argument limit in Lua which is lower than the stack limit. I'm not sure what happens there, but if it is a stack based panic, (or any panic?) it is a bug. Two, I believe that if you recurse over and over between lua -> rust -> lua -> rust etc, and call rlua API functions, you might get a stack panic. I think for trusted lua code, this is morally equivalent to a regular stack overflow in plain rust, which is already.. well it's not a panic but it's some kind of safe crash I'm not sure, so I think this is acceptable. For *untrusted* lua code, this could theoretically be a problem if the API provided a callback that would call back into lua, then some lua script could force a stack based panic. There are so many concerns with untrusted lua code, and this library is NOT safe enough yet for untrusted code (it doesn't even provide an option to limit lua to the safe API subset yet!), so this is not currently an issue. When the library provides support for "safe lua", it should come with big warnings anyway, and being able to force a stack panic is pretty minor in comparison. I think if there are other ways to cause unbounded stack usage, that it is a bug, or there can be an error just for that situation, like argument count limits. This commit also fixes several stupid bugs with tests, stack checking, and panics.
2017-06-25 15:52:32 -05:00
let globals = lua.globals();
2017-05-21 20:47:32 -05:00
globals.set("string_var", "hello")?;
globals.set("int_var", 42)?;
2017-05-21 20:47:32 -05:00
assert_eq!(globals.get::<_, String>("string_var")?, "hello");
assert_eq!(globals.get::<_, i64>("int_var")?, 42);
2017-05-21 20:47:32 -05:00
// You can load and evaluate Lua code. The returned type of `Lua::load` is a builder
// that allows you to change settings before running Lua code. Here, we are using it to set
2019-10-17 10:59:33 -05:00
// the name of the laoded chunk to "example code", which will be used when Lua error
// messages are printed.
2017-05-21 20:47:32 -05:00
2019-10-17 10:59:33 -05:00
lua.load(
r#"
2017-05-21 20:47:32 -05:00
global = 'foo'..'bar'
"#,
2019-10-17 10:59:33 -05:00
)
.set_name("example code")?
.exec()?;
assert_eq!(globals.get::<_, String>("global")?, "foobar");
2017-05-21 20:47:32 -05:00
2019-10-17 10:59:33 -05:00
assert_eq!(lua.load("1 + 1").eval::<i32>()?, 2);
assert_eq!(lua.load("false == false").eval::<bool>()?, true);
assert_eq!(lua.load("return 1 + 2").eval::<i32>()?, 3);
2017-05-21 20:47:32 -05:00
// Use can use special `chunk!` macro to use Rust tokenizer and automatically capture variables
let a = 1;
let b = 2;
let name = "world";
lua.load(chunk! {
print($a + $b)
print("hello, " .. $name)
})
.exec()?;
2019-10-17 10:59:33 -05:00
// You can create and manage Lua tables
2017-05-21 20:47:32 -05:00
let array_table = lua.create_table()?;
2017-05-21 20:47:32 -05:00
array_table.set(1, "one")?;
array_table.set(2, "two")?;
array_table.set(3, "three")?;
assert_eq!(array_table.len()?, 3);
2017-05-21 20:47:32 -05:00
let map_table = lua.create_table()?;
2017-05-21 20:47:32 -05:00
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 `Table` back into Lua
2017-05-21 20:47:32 -05:00
globals.set("array_table", array_table)?;
globals.set("map_table", map_table)?;
2017-05-21 20:47:32 -05:00
2019-10-17 10:59:33 -05:00
lua.load(
r#"
2019-10-17 10:59:33 -05:00
for k, v in pairs(array_table) do
print(k, v)
end
2017-05-21 20:47:32 -05:00
2019-10-17 10:59:33 -05:00
for k, v in pairs(map_table) do
print(k, v)
end
"#,
2019-10-17 10:59:33 -05:00
)
.exec()?;
2017-05-21 20:47:32 -05:00
2019-10-17 10:59:33 -05:00
// You can load Lua functions
2017-05-21 20:47:32 -05:00
let print: Function = globals.get("print")?;
2017-05-21 20:47:32 -05:00
print.call::<_, ()>("hello from rust")?;
// This API generally handles variadics using tuples. This is one way to call a function with
// multiple parameters:
2017-05-21 20:47:32 -05:00
print.call::<_, ()>(("hello", "again", "from", "rust"))?;
2017-05-21 20:47:32 -05:00
// But, you can also pass variadic arguments with the `Variadic` type.
print.call::<_, ()>(Variadic::from_iter(
["hello", "yet", "again", "from", "rust"].iter().cloned(),
))?;
// You can bind rust functions to Lua as well. Callbacks receive the Lua state inself as their
// first parameter, and the arguments given to the function as the second parameter. The type
// of the arguments can be anything that is convertible from the parameters given by Lua, in
// this case, the function expects two string sequences.
let check_equal = lua.create_function(|_, (list1, list2): (Vec<String>, Vec<String>)| {
// This function just checks whether two string lists are equal, and in an inefficient way.
2019-10-17 10:59:33 -05:00
// Lua callbacks return `mlua::Result`, an Ok value is a normal return, and an Err return
// turns into a Lua 'error'. Again, any type that is convertible to Lua may be returned.
Ok(list1 == list2)
})?;
globals.set("check_equal", check_equal)?;
2017-05-21 20:47:32 -05:00
// You can also accept runtime variadic arguments to rust callbacks.
2017-06-25 21:25:28 -05:00
let join = lua.create_function(|_, strings: Variadic<String>| {
2017-06-15 09:26:39 -05:00
// (This is quadratic!, it's just an example!)
Ok(strings.iter().fold("".to_owned(), |a, b| a + b))
})?;
globals.set("join", join)?;
2017-05-21 20:57:15 -05:00
2017-06-15 09:26:39 -05:00
assert_eq!(
2019-10-17 10:59:33 -05:00
lua.load(r#"check_equal({"a", "b", "c"}, {"a", "b", "c"})"#)
.eval::<bool>()?,
2017-06-15 09:26:39 -05:00
true
);
assert_eq!(
2019-10-17 10:59:33 -05:00
lua.load(r#"check_equal({"a", "b", "c"}, {"d", "e", "f"})"#)
.eval::<bool>()?,
2017-06-15 09:26:39 -05:00
false
);
2019-10-17 10:59:33 -05:00
assert_eq!(lua.load(r#"join("a", "b", "c")"#).eval::<String>()?, "abc");
// Callbacks receive a Lua state as their first parameter so that they can use it to
// create new Lua values, if necessary.
let create_table = lua.create_function(|lua, ()| {
let t = lua.create_table()?;
t.set(1, 1)?;
t.set(2, 2)?;
Ok(t)
})?;
globals.set("create_table", create_table)?;
assert_eq!(lua.load(r#"create_table()[2]"#).eval::<i32>()?, 2);
2017-05-21 20:47:32 -05:00
// You can create userdata with methods and metamethods defined on them.
2017-06-25 21:25:28 -05:00
// Here's a worked example that shows many of the features of this API
// together
2017-05-21 20:47:32 -05:00
#[derive(Copy, Clone)]
struct Vec2(f32, f32);
impl UserData for Vec2 {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
2017-08-01 13:09:47 -05:00
methods.add_method("magnitude", |_, vec, ()| {
2017-05-21 20:47:32 -05:00
let mag_squared = vec.0 * vec.0 + vec.1 * vec.1;
Ok(mag_squared.sqrt())
2017-05-21 20:47:32 -05:00
});
methods.add_meta_function(MetaMethod::Add, |_, (vec1, vec2): (Vec2, Vec2)| {
Ok(Vec2(vec1.0 + vec2.0, vec1.1 + vec2.1))
2017-05-21 20:47:32 -05:00
});
}
}
let vec2_constructor = lua.create_function(|_, (x, y): (f32, f32)| Ok(Vec2(x, y)))?;
globals.set("vec2", vec2_constructor)?;
2017-05-21 20:47:32 -05:00
2018-05-02 19:05:43 -05:00
assert!(
2019-10-17 10:59:33 -05:00
(lua.load("(vec2(1, 2) + vec2(2, 2)):magnitude()")
.eval::<f32>()?
- 5.0)
.abs()
2018-05-02 19:05:43 -05:00
< f32::EPSILON
);
2017-05-21 20:47:32 -05:00
// Normally, Rust types passed to `Lua` must be `'static`, because there is no way to be
// sure of their lifetime inside the Lua state. There is, however, a limited way to lift this
// requirement. You can call `Lua::scope` to create userdata and callbacks types that only live
// for as long as the call to scope, but do not have to be `'static` (and `Send`).
{
let mut rust_val = 0;
lua.scope(|scope| {
// We create a 'sketchy' Lua callback that holds a mutable reference to the variable
// `rust_val`. Outside of a `Lua::scope` call, this would not be allowed
// because it could be unsafe.
lua.globals().set(
"sketchy",
scope.create_function_mut(|_, ()| {
rust_val = 42;
Ok(())
})?,
)?;
lua.load("sketchy()").exec()
})?;
assert_eq!(rust_val, 42);
}
// We were able to run our 'sketchy' function inside the scope just fine. However, if we
2019-10-17 10:59:33 -05:00
// try to run our 'sketchy' function outside of the scope, the function we created will have
// been invalidated and we will generate an error. If our function wasn't invalidated, we
2019-10-17 10:59:33 -05:00
// might be able to improperly access the freed `rust_val` which would be unsafe.
assert!(lua.load("sketchy()").exec().is_err());
2017-05-21 20:47:32 -05:00
Ok(())
}