mlua-rs / rlua Goto Github PK
View Code? Open in Web Editor NEWHigh level Lua bindings to Rust
License: Other
High level Lua bindings to Rust
License: Other
Lua scripts executed from rlua do not seem to have a function table.getn
. I would've thought that this line would ensure it's available: https://github.com/chucklefish/rlua/blob/master/src/lua.rs#L484
But it seems that while there's a global table
, table.getn
is nil
. Is this expected?
Simplest way to reproduce this would be to run the repl example and run table.getn({1,2})
.
Reproduction:
extern crate rlua;
use rlua::*;
fn main() {
let lua = Lua::new();
let thrd_main = lua.create_function(|lua, _| {
lua.pack(())
});
lua.globals().set("main", thrd_main).unwrap();
let thrd: Thread = lua.eval("coroutine.create(main)", None).unwrap();
thrd.resume::<_, ()>(()).unwrap();
}
This hits an assertion:
bug: lua/lapi.c:182: lua_settop: Assertion `(-(idx+1) <= (L->top - (func + 1))) && "invalid new top"' failed.
Notably, when replacing the Lua code with coroutine.create(function() end)
it works.
Currently this compiles, and that is a problem because lua
does not have a stable address. Closures should go back to having a 'static lifetime.
let lua = Lua::new();
let globals = lua.globals();
globals.set("boom", lua.create_function(|_, _| {
lua.pack(lua.eval::<i32>("1 + 1", None)?)
})).unwrap();
Currently, rlua makes sure to only call Lua API functions that can cause an error from within error_guard
, which uses lua_pcall
to run a Rust closure in a protected environment. This causes all Lua errors to be caught by lua_pcall
.
Since Lua's error handling uses setjmp
and longjmp
to do non-local control flow, an error will longjmp
to the lua_pcall
, skipping across the Rust closure passed to error_guard
. According to IRC this is undefined behaviour in Rust. EDIT: People are also claiming that it's unsafe in this user forum thread.
Fixing this is hard. It would require that all uses of lua_pcall
only call C functions. And calling lua_error
from Rust would never be a safe thing to do, since it must skip across a Rust stack frame.
If this is really UB, the only way to write a safe Lua wrapper would involve writing C code. Not sure if that's really the case, though...
The manual states:
Moreover, if the finalizer marks a finalizing object for finalization again, its finalizer will be called again in the next cycle where the object is unreachable.
This means that a destructor assigned to the __gc
metamethod might be run multiple times on the same userdata. Inside fn destructor
, rlua currently just does this:
let obj = &mut *(ffi::lua_touserdata(state, 1) as *mut T);
mem::replace(obj, mem::uninitialized());
0
If one figures out a way to resurrect the userdata object from within drop
, this leads to a double free. However, I am not sure if this is possible, since all userdata must outlive 'static
.
If this is by design, it would be helpful to document that. In general, rlua
s internals are barely documented which makes it hard to understand and debug stuff like #18.
EDIT: See comment below for use-after-free demonstration
I believe wrapping Lua
in Arc<Mutex<...>>
should allow passing it to other threads and synchronized access?
Right now it's this:
error[E0277]: the trait bound `*mut rlua::ffi::lua_State: std::marker::Send` is not satisfied in `rlua::Lua`
--> src/main.rs:29:29
|
29 | let event_loop_thread = thread::spawn({
| ^^^^^^^^^^^^^ `*mut rlua::ffi::lua_State` cannot be sent between threads safely
|
= help: within `rlua::Lua`, the trait `std::marker::Send` is not implemented for `*mut rlua::ffi::lua_State`
= note: required because it appears within the type `rlua::Lua`
= note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Mutex<rlua::Lua>`
= note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Arc<std::sync::Mutex<rlua::Lua>>`
= note: required because it appears within the type `[closure@src/main.rs:32:9: 55:10 lua:std::sync::Arc<std::sync::Mutex<rlua::Lua>>, barrier:std::sync::Arc<std::sync::Barrier>]`
= note: required by `std::thread::spawn`
In the Guided Tour example, the comment above the .scope
code could imply that no GC is running while using the scope. At least that's how I understood it at first.
After a small test I'm pretty certain that the GC is running when inside a scope, so I guess this is just an unfortunately phrased comment.
Currently, adding a variant to Error
(or changing an existing one) is a breaking change. This happens pretty frequently during development. We could prevent that by making Error
a struct with a few helper methods to create a specific error, and make the internal enum private so it's not part of the API.
This is probably not that important right now, but when rlua gets closer to a 1.0 release it could allow more freedom in expanding rlua's internals (better error messages, more distinguished error cases without a breaking change). I'm just opening this issue to get a bit of feedback.
Hi, found this note in lua-jit-sys crate:
Compatability with rlua and hlua
I will try to get rlua and hlua working
I wonder how much effort it is to add LuaJIT support to rlua? I found rlua has the most idiomatic and safe API to Lua out there, and it fits my needs almost perfectly - just need the LuaJIT there.
Function
is basically a wrapper of Lua
and since Lua
itself is Send
, is it possible to implement Send
for Function
?
As of right now, when creating a new instance of the Lua structure, all the lua libraries are automatically loaded. This is, as written in the README, undesirable.
Is there any reason this is currently like this? What do I need to know before trying to change the API to make libraries loading into a separate functions?
rlua doesn't tag its releases, which can be annoying when you want to check the source of a particular version.
After a long debugging session, I've noticed that cargo test --release
fails with a segfault on the current nightly.
This might as well be a regression of the compiler, I'm not yet sure.
Currently conversion of Rust types to Lua Value
s is uses FromLua
and ToLua
traits. However, it might be cumbersome to implement these traits manually. One solution is to implement derive procedural macro. However, it is quite a lot of work and introduces extra dependency for end user.
On the other hand, a very similar data type is used in serde_json
- Value. With Serializer
/Deserializer
traits implemented, basically any data type with Serialize
/Deserialize
trait can be converted to/from it.
I have test implementation of Serializer
in zrkn/rlua (mostly copied from serde_json
). In my project I only need to convert Rust values to Lua, so it works for me. However if you would consider merging this feature upstream I can implement Deserializer
too and make pull request.
Hit this while writing docs and an example for LuaThread::resume
. Take this code:
extern crate rlua;
use rlua::*;
fn main() {
let lua = Lua::new();
let thread: LuaThread = lua.eval(r#"
coroutine.create(function(arg)
assert(arg == 42)
local yieldarg = coroutine.yield(123)
assert(yieldarg == 43)
return 987
end)
"#).unwrap();
assert_eq!(thread.resume::<_, u32>(42).unwrap(), Some(123));
assert_eq!(thread.resume::<_, u32>(43).unwrap(), Some(987));
assert_eq!(thread.resume::<_, u32>(()).unwrap(), None);
}
Executing it hits this assertion inside Lua:
rust_out: lua/ldo.c:663: lua_resume: Assertion `(((L->status == 0) ? nargs + 1 : nargs) < (L->top - L->ci->func)) && "not enough elements in the stack"' failed.
The rlua
crate is already a namespace for the contents, so they don't need a prefix for disambiguation. Removing the prefix would allow writing shorter and slightly clearer code in my opinion.
Downside: use rlua::*;
imports too much (including a Result
type alias shadowing Rust's own result type). This can be mitigated somewhat by introducing a prelude
module like many libs do. It's also not clear to me if renaming the LuaString
type to String
is a good idea.
(also, LightUserData
is the only type that's not prefixed)
See rust-lang/rust#48251 for details.
We already have a ToLua
impl for Option
, which will push the contained value if it exists, and nil
otherwise. This maps pretty well into Lua's world.
Another common Lua pattern occurs when dealing with fallible operations like io.open
, which either returns the opened file on success, or nil
followed by an error message on failure. It would be nice if I could map Rust's Result
to this idiom. This would require a ToLuaMulti
impl instead of a ToLua
impl, though (since the operation can push nil
and the error message).
When playing around trying to make a prototype for a system I'm developing, I found that I bumped into a wall when using Lua.create_function
. It has a lifetime bound specified as 'static
which caused trouble, but when I tried changing it to 'lua
it worked and it doesn't seem to have caused trouble elsewhere. So my question is why the boundary is 'static
instead of 'lua
. I'm pretty new to Rust so I may have missed something very obvious here, but I thought I'd ask anyway.
Thanks!
Hi,
Lua's default tostring
implementation just calls luaL_tolstring
. For userdata
, thread
s, ..., this creates a string of the form [typename] [pointer]
where the pointer comes from lua_topointer()
.
The current plan in way-cooler is to reimplement parts of awesome. Awesome implements a similar __tostring
metamethod: This produces the name of the type (e.g. button
) followed by the raw pointer. Right now I see no way to implement this with rlua. Taking the address of an AnyUserData
(via &foo as *const _
) does not work, since this just produces a "random pointer" to the stack.
(As I just learned when looking up the code, I could just add a __name
field to the meta-table... interesting; thanks for making me solve my own problems! Still, I'll leave this paragraph in since it still might be nice to be able to implement this.)
The above might not be all that convincing (you could just tell me to "fake" a pointer, I guess), so here is another use case:
Awesome has objects called "tags". These are accessible outside of Lua (via X11). To make it possible for Lua to create (easy) and destroy (hard!) tags, tag objects have an activated
property that can be set to true/false. When true, awesome uses luaL_ref
to create a reference to the tag, saves it in a global list and makes it accessible externally.
I have problems reimplementing this with rlua. My idea was to save a Table in the registry. When a tag becomes activated, I append the tag to the table (table.set(table.len() + 1, object)
). However, when deactivating a tag, I have problems finding the right entry in the table. I do not want to use something like PartialEq
, since that compares for equality, not object identity.
I do not have a really good reason for why I don't just do table.set(object, true)
(i.e. using the object as key and let Lua figure out object identity) except for compatibility with awesome.
I think both of these problems could be fixed by providing wrappers for lua_topointer
on AnyUserData
, although I have to admit that this feels a bit hackish. Of course, I am always open for other suggestions as well. :-)
Edit: Well... okay, __name
is not accessible through UserDataMethods
, so that one does not help me either.
When a coroutine checks its own status, ThreadStatus::Dead
is returned.
I'm not sure if this is intentional, Active
would make a bit more sense, but that would mean that we can't know in advance whether calling resume
will work. Maybe Active
should be split into Running
and Ready
/Resumable
/Waiting
or something else?
Reproduction:
extern crate rlua;
use rlua::*;
fn main() {
let lua = Lua::new();
let f = lua.create_function(|lua, args| {
let thrd: Thread = lua.unpack(args)?;
println!("{:?}", thrd.status());
lua.pack(())
});
let thrd = lua.create_thread(f);
thrd.resume::<_, ()>(thrd.clone()).unwrap();
}
Callbacks are not currently placed inside a RefCell, and are also FnMuts (not Fns), so if you call a callback inside itself, you've got mutability and aliasing.
I think all userdatas should be inside RefCell to ensure this problem is avoided in the future, we could also provide the ability to add Fn (not FnMut) callbacks, but it kind of doubles the API surface area, unless we do some trait trickery. I think just using RefCell is fine for the moment.
I need to store a user-provided Lua function in a struct that implements UserData
. I tried using UserDataMethods
to add a method that takes in a function and stores it but I keep running into lifetime errors.
rlua::Lua
is expected to live for the entirety of the program (and in fact I put it in a lazy_static!
so that its address is always stable), but I can't find a way to indicate in the custom user data method that rlua::Lua
will be 'static
and there's no way for me to convince it that the anonymous 'lua
lifetime for the passed in rlua::Lua
reference will live longer than the reference I store in the UserData
.
Here's a minimized example of the issue I'm running into:
use rlua;
// How long the function lives, should be == 'lua
struct MyUserData<'function> {
function: Option<rlua::Function<'function>>
}
// HAS to be static, due to `UserData` requirements...
impl UserData for MyUserData<'static>
{
fn add_methods(methods: &mut rlua::UserDataMethods<Self>) {
methods.add_method_mut("test", test_function);
}
}
// lifetime issue occurs in this function
// is there some way for me to prove that `this` lives as long as `lua`??
fn test_function(lua: &rlua::Lua, this: &mut MyUserData, func: rlua::Function)
-> rlua::Result<()> {
this.function = Some(func);
}
fn main() {}
extern crate rlua;
struct Unconvertible;
impl <'lua> rlua::FromLua<'lua> for Unconvertible {
fn from_lua(_: rlua::Value<'lua>, _: &'lua rlua::Lua) -> rlua::Result<Self> {
Err(rlua::Error::ExpiredUserData)
}
}
struct MyData;
impl rlua::UserData for MyData {
}
fn main() {
let lua = rlua::Lua::new();
let data = lua.create_userdata(MyData).unwrap();
data.get_user_value::<Unconvertible>().unwrap();
}
Output: (with RUST_BACKTRACE=1
):
thread 'main' panicked at 'rlua internal error: expected stack to be 0, got 1', /home/psychon/.cargo/registry/src/github.com-1ecc6299db9ec823/rlua-0.12.0/src/util.rs:47:4
stack backtrace:
0: std::sys::imp::backtrace::tracing::imp::unwind_backtrace
1: std::sys_common::backtrace::_print
2: std::panicking::default_hook::{{closure}}
3: std::panicking::default_hook
4: std::panicking::rust_panic_with_hook
5: std::panicking::begin_panic
6: std::panicking::begin_panic_fmt
7: rlua::util::stack_guard
at ./<panic macros>:7
8: rlua::userdata::AnyUserData::get_user_value
at /home/psychon/.cargo/registry/src/github.com-1ecc6299db9ec823/rlua-0.12.0/src/userdata.rs:426
9: rlua_test::main
at src/main.rs:19
10: __rust_maybe_catch_panic
11: std::rt::lang_start
12: main
13: __libc_start_main
14: _start
What I think happens:
This is the code for get_user_value
:
pub fn get_user_value<V: FromLua<'lua>>(&self) -> Result<V> {
let lua = self.0.lua;
unsafe {
stack_guard(lua.state, 0, || {
check_stack(lua.state, 2);
lua.push_ref(lua.state, &self.0);
ffi::lua_getuservalue(lua.state, -1);
let res = V::from_lua(lua.pop_value(lua.state), lua)?;
ffi::lua_pop(lua.state, 1);
Ok(res)
})
}
}
In the above, if from_lua
fails, then the lua_pop
is not executed and the stack_guard
notices that something is wrong.
I have not really looked at the remaining code in rlua, but I guess that this problems also exists in other places. For example, I bet I could trigger a panic with set_user_value
by making to_lua
fail.
CC @Timidger
I'm having a bit of difficulty architecting a scripting extension to my entity component system using rlua to support user-scripted behaviours. The idea is to have the user define a table in a Lua script resembling the following:
local Test = { }
function Test:Init()
-- Do some stuff
end
function Test:Update(delta)
-- Do some stuff
end
function Test:Destroy()
-- Do some stuff
end
return Test
Now, at a basic level, I'd like to be able to associate these table definitions with an internal entity ID. So, I'd like somewhere to store a HashMap<Entity, Table>
. On an update event, I'd like to loop through the tables and execute the appropriate function in the Lua context. The issue I'm running into currently is lifetimes. With the following astoundingly naive solution:
pub struct ScriptSystem<'a> {
host: Lua,
initialized: BitSet,
behaviours: HashMap<Entity, Table<'a>>,
}
impl<'a> ScriptSystem<'a> {
pub fn new() -> Self {
ScriptSystem {
host: Lua::new(),
initialized: BitSet::new(),
behaviours: HashMap::new(),
}
}
}
impl<'a, 'b> System<'a> for ScriptSystem<'b> {
type SystemData = (
Entities<'a>,
ReadStorage<'a, Script>,
Fetch<'a, AssetStorage<ScriptAsset>>,
Fetch<'a, Time>,
);
fn run(&mut self, (entities, script, storage, time): Self::SystemData) {
let behaviour = self.host.eval::<Table>(asset.source.as_str(), Some(asset.name.as_str()));
self.behaviours.insert(entity, behaviour);
}
}
I run into predictable lifetime errors, because the lifetime of the Table
value returned from the eval()
call would outlive the borrowed content self.host
but must also adhere to the lifetime parameter 'b
and so survive with the struct. I have tried every workaround I know of to fix this issue, including wrapping both the Lua instance and HashMap in a common struct with a shared lifetime. I think it's time to face the fact that I'm probably just doing something plain wrong, and was curious if there is a solution to this sort of use case using rlua.
"map" => {
let eval_res = lua.eval::<HashMap>(code, None);
println!("val = {:?}", eval_res);
}
"string" => {
let eval_res = lua.eval::<Vec>(code, None);
println!("val = {:?}", eval_res);
}
Something like this?
Hello, and thanks for this library, it looks great.
I'm trying to write a configurable/scriptable program where one can register functions from a Lua script to be used later from a GUI Rust program. Running the following in release mode (Linux 64 bit, Rust 1.27.2) creates a segfault when program
is dropped. Am I doing something wrong or strange?
extern crate rlua;
use rlua::prelude::*;
#[derive(Debug)]
struct Program<'a> {
funcs: Vec<(String,LuaFunction<'a>)>,
}
fn mk_program<'a>(lua :&'a Lua, script :&str) -> LuaResult<Program<'a>> {
let mut program = Program { funcs: Vec::new()};
lua.scope(|scope| {
lua.globals().set("register", scope.create_function_mut(|_, tbl:LuaTable| {
let name :String = tbl.get("name")?;
let func :LuaFunction = tbl.get("func")?;
program.funcs.push((name,func));
Ok(())
})?)?;
lua.eval::<()>(script, None)?;
Ok(())
})?;
Ok(program)
}
fn main(){
let lua = Lua::new();
let program = mk_program(&lua, r#"register { name = "test", func = function () end }"#).unwrap();
println!("{:?}", program);
}
GDB output:
Program { funcs: [("test", Function(Ref(4)))] }
Program received signal SIGSEGV, Segmentation fault.
rlua::lua::Lua::drop_ref::h44c6ee14693a031e (self=0x7fffffffd278, lref=0x7ffff6a50018)
at /home/bjlut/.cargo/registry/src/github.com-1ecc6299db9ec823/rlua-0.14.0/src/lua.rs:763
763 ffi::lua_pushnil((*extra).ref_thread);
(gdb) bt
#0 rlua::lua::Lua::drop_ref::h44c6ee14693a031e (self=0x7fffffffd278, lref=0x7ffff6a50018)
at /home/bjlut/.cargo/registry/src/github.com-1ecc6299db9ec823/rlua-0.14.0/src/lua.rs:763
#1 _$LT$rlua..types..LuaRef$LT$$u27$lua$GT$$u20$as$u20$core..ops..drop..Drop$GT$::drop::h5c2fb308405493c3 (self=0x7ffff6a50018)
at /home/bjlut/.cargo/registry/src/github.com-1ecc6299db9ec823/rlua-0.14.0/src/types.rs:92
#2 0x0000555555562ad5 in core::ptr::drop_in_place::h4eff9b84948e99de () at /checkout/src/libcore/ptr.rs:59
#3 core::ptr::drop_in_place::hc0a0e904c5e46691 () at /checkout/src/libcore/ptr.rs:59
#4 core::ptr::drop_in_place::hf24a5bf0280dbaeb () at /checkout/src/libcore/ptr.rs:59
#5 core::ptr::drop_in_place::h4dff6797faf34f16 () at /checkout/src/libcore/ptr.rs:59
#6 _$LT$alloc..vec..Vec$LT$T$GT$$u20$as$u20$core..ops..drop..Drop$GT$::drop::h6e6cc1341dca5ef0 (self=<optimized out>)
at /checkout/src/liballoc/vec.rs:2166
#7 core::ptr::drop_in_place::hdeb56b48f40b6a81 () at /checkout/src/libcore/ptr.rs:59
#8 core::ptr::drop_in_place::heebf2eae8bc36947 () at /checkout/src/libcore/ptr.rs:59
#9 luasegfault::main::h91edc85fef633fb6 () at src/main.rs:28
#10 0x0000555555560763 in std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hf36a085e69e29833 () at /checkout/src/libstd/rt.rs:74
#11 0x00005555555ae183 in std::rt::lang_start_internal::_$u7b$$u7b$closure$u7d$$u7d$::h86ba874310c8f41e () at libstd/rt.rs:59
#12 std::panicking::try::do_call::h64129d2b0e54f3b8 () at libstd/panicking.rs:310
#13 0x00005555555c4dca in __rust_maybe_catch_panic () at libpanic_unwind/lib.rs:105
#14 0x00005555555b2156 in std::panicking::try::hceef11cfeb87cfe0 () at libstd/panicking.rs:289
#15 std::panic::catch_unwind::h331e7e117781c30b () at libstd/panic.rs:374
#16 std::rt::lang_start_internal::h6264a91317866dd6 () at libstd/rt.rs:58
#17 0x0000555555562d5a in main ()
It's sufficient if the userdata outlives the Lua state. That way it can store references to other Lua objects or the main Lua state, which makes userdata more flexible.
I've noticed that LuaTable::for_each_array_value
and LuaTable::array_values
have somewhat surprising behaviour. First of all, they don't really match anything in the Lua API (the manual doesn't define what an array is, only sequences are defined). Second, they require the table to exclusively hold natural numbers as keys, something unseen in the Lua API or standard library (which just ignore the non-sequence part of a table).
Another unexpected behaviour occurs when iterating over a table with "holes", like { [1] = "bla", [2] = "qux", [123] = "oops" }
, then the missing elements are still taken into account and the closure passed to for_each_array_value
is called 121 times with a nil
value. While this is pretty much the only usable behaviour since the key isn't passed to the closure, this isn't quite what I expected (eg. ipairs
would ignore the 123 key and only iterate over 1 and 2).
My initial solution for this would've been to change the methods to act on the sequence part of the table instead (and rename them accordingly), however, the current behaviour is used by the FromLua
impls of HashSet
, BTreeSet
and Vec
, where it does make more sense (we return an error instead of ignoring the non-sequence part of the table). Not sure what's the best way forward for this API.
rlua REPL session:
> xpcall(error, function(...) print("caught error: ", ...) return "errör" end, "testerror")
caught error: bad argument #2 to 'error' (number expected, got string)
Boolean(false) String(String(LuaRef(4)))
> xpcall()
repl: lua/lapi.c:227: lua_copy: Assertion `(((to) != (&luaO_nilobject_))) && "invalid index"' failed.
The first one: error
is not called with the right arguments, it should only get "testerror".
The second one is just a missing check for enough arguments.
pcall
also has problems:
> print(pcall())
false attempt to call a string value
This is a tracking issue for the remaining cases where Lua code can cause a Rust panic or a direct abort. This is generally unwanted, because untrusted Lua code should not be able to abort the Rust program. Although panics can be caught on the Rust-side, it is intended that all errors caused by Lua are propagated using LuaResult
.
Note that fixing these issues will still not make it advisable to run arbitrary untrusted Lua code. Proper sandboxing support and execution limits need to be exposed first.
UserData
directly causes a panic. Instead, a Lua error should be raised. This error can be caught by Lua (this isn't very useful, but also not a big problem) or will propagate to the Rust code that caused the Lua code to run.Variadic
s or MultiValue
s. When Rust calls a function with too many arguments, an Error
should be directly returned. When Lua calls a Rust callback with too many arguments, the Lua runtime should already handle this. However, a Rust callback can also return too many values (Lua "only" guarantees that at least 1000 values can be returned). This can be handled by raising a Lua error. Alternatively, we can define this as a bug on the Rust side and cause a panic. This can lead to subtle bugs, however, when the number of values depends on the inputs from Lua or is otherwise unclear.__gc
cause an abort. Raising a Lua error from here is unsafe, so it is caught and the program is aborted. This might be a reasonable default behaviour in some cases, but it would be nice if a user-defined callback could be provided.I hope these are all cases. If not, feel free to comment/edit, of course.
I found a strange issue with using xpcall
that I can't reproduce in other Lua 5.3 interpreters. I tested this using the system lua (e.g I compiled it with --no-default-features
on the repl and I found it through a very hard to follow bug while working on Way Cooler).
Here's the basic steps to reproduce:
Run xpcall(function(foo) return foo end, print, "foo")
in both interpreters (note it doesn't really matter what the functions are, the first function has to return something though)
On my system lua (Lua 5.3.4) it returns true
and "foo"
(as expected)
On rlua it returns true
function
"foo"
.
The second function it returns is the error function, you can see that by running this in the rlua interpreter:
(function(a, b, c) b("a") end )(xpcall(function(foo) return foo end, print, "foo"))
=> prints "a"
Can I catch and Faulty lua code, and get some stack trace without panicking the main Rust code?
At the moment I'm using specs
and rlua
to write a simple RTS for research-level AI. I won't go into the details, but for various reasons (including tree search, game replays, speculative simulation, and so on) I really need to serialize the entire game state from any frame so that it's arbitrarily rewindable and deterministic on rewind.
At the moment, the lack of serialization is constraining. I basically have to assume the Lua stores no data, and likewise the Rust side must immediately convert any returned Lua data into native Rust types and write it elsewhere. This limits my scripting potential heavily without mucking with the core engine, because I need to essentially implement piecemeal triggers in Rust and use Lua as a simple configuration language (basically just return a table of prebaked entities, triggers and parameters which Rust then executes).
Currently, my strategy is just to serialize the file path of the script, and on startup load it (on rewind I just assume the file is constant).
I'm not asking for full Serde
here since I doubt it's even possible (esp. once references get involved), but it would be nice if there was some way to serialize certain data and (pipe dream) Function types, and reattach them to a new context, even if there end up being some constraints. I understand this is likely a Hard Problem™ but I figured I'd file an issue just to see if any partial version of this is even possible.
You write:
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 have non-mutable borrows to the main Lua object, so there can be multiple handles or long lived handles
Targets Lua 5.3
What is what?
What is hlua and which one your project?
let on_function = lua.create_function(|_, (name, handler): (String, fn(String))| {
handler(name);
Ok(())
})?;
I've also attempted with rlua::Function
, but that seems to not work either.
Error:
error[E0277]: the trait bound `(std::string::String, fn(std::string::String)): rlua::FromLuaMulti<'_>` is not satisfied
--> src/modules/api/event_handler.rs:17:25
|
17 | let on_function = lua.create_function(|_, (name, handler): (String, fn(String))| {
| ^^^^^^^^^^^^^^^ the trait `rlua::FromLuaMulti<'_>` is not implemented for `(std::string::String, fn(std::string::String))`
|
= help: the following implementations were found:
<(B, A) as rlua::FromLuaMulti<'lua>>
Is there someway to pass a function that I'm missing?
Hey there,
The awesome lua library (which I'm trying to run with these bindings) makes great use of the debug
functionality of Lua (specifically debug.traceback
), but it's not included in these bindings. I could make it myself, but I just wanted to double check to see why it's not included.
This might be a safety nightmare, so I wanted to get your input before looking into implementation. Here are the production use-cases:
And then there's the typical debugging tasks, but I think those can be done within lua with the current debug library.
From #52, it looks like the debug library is considered "unsafe", and I'm not sure if this can be done safely. In any case, I'd really like be able to handle the above use cases.
I created a similar issue on hlua (#194), just in case discussion in the other project is useful here.
I'm willing to do work on this, but I'd like to know your thoughts on feasibility first.
EDIT: This is essentially the first bullet point in the README (and perhaps the second).
As seen here, rlua checks for "<eof>" at the end of error messages and returns LuaErrorKind::IncompleteStatement if it finds it.
One issue I have with this is that it causes tests::test_eval to fail when using LuaJIT, because the error message ends with "'<eof>'" (notice the single quotes). I realize that might not be a good enough reason to change it.
But my OTHER issue is that it just seems pointless to have it at all. Why not just return a LuaErrorKind::ScriptError in both cases? (the comment there even suggests that it would be better to remove it)
I have a map of methods that I want to add to my userdata. However, I can't pass anything to UserData::add_methods
. Is there a way to set methods after I created the user data?
This assert is missing an abs() call to make the < EPSILON
comparison a valid substitution for equality.
As it stands, the function returning any magnitude lower than 5.0 will pass the assert correctly.
While using rlua
, I encountered a lot of lifetime problem which are already described in previous issues (e.g. #20 (comment), #41, #74...). After reading those issues, using rlua
becomes much easier.
It would be great if we can collect these design rationale into a FAQ document for beginners to lookup. I can help if you're ok about the idea.
Here's a list of issues/comment I think are really helpful:
'static
lifetime is required when passing data to Lua
?The reason I mention the GC is because that is the reason WHY userdata must be 'static: since we don't know when Lua will get around to garbage collecting our userdata, we can't make any assumptions about the lifetimes of any references in the userdata. Lua::scope forces the userdatas to be destructed at the exit of the scope.
Table
, Function
, or other rlua
reference types outside of Lua?There are two rules about rlua reference types that you're butting up against:
rlua reference types (Table, Function, etc) hold references to Lua, and are not really designed to be stored along-side Lua, because that would require self borrows. You CAN make structs in Rust that self borrow, but you really don't want to go down that road, it's very complex and you 99.999% of the time don't need it.
rlua reference types are also not designed to be stored inside userdata inside Lua. When I say userdata here, I mean both actual UserData types and also things like Rust callbacks. For one, there are some actual safety issues if you were able to do that, but more importantly Lua itself is not really designed for this either. Lua's C API does not provide a way of telling Lua that a userdata contains a registry reference, so you would end up potentially making something that cannot be garbage collected. The actual issue is very complicated but you can read about it some here.
Coming from #21 (comment), here are a few projects I found that could help with a native implementation:
Lexer / Parser
Interpeter:
Other non-PuC Lua projects:
Lua::create_function accepts callables of 'static lifetime. This obviously disallows passing a closure with an environment, since they are not 'static. This is raises a question: How can one acces data outside of LuaFunction?
How can one achieve something like the following?
let house = House::new(); // Interior mutability type
globals.set("foo", lua.create_function(|_, _| house.set_on_fire()));
What's the usual pattern to solve this issue?
Is it possible to expose a method that consumes self
in UserDataMethods
? The motivation is that I want to expose the commit method of postgres connections, however the typical add_method
/add_method_mut
won't work.
This makes the raw registry API rather useless very hard to use since you can't name the type. The string-based one still works fine, of course.
I'm curious about the rationale behind allowing mutations in rlua
types without mut
? I get that it may be more convenient, but is there some justification as to why it's not just undermining the intent of Rust's type system?
It seems like several people have struggled with putting Rust values into Lua as UserData and then later extracting them from Lua again, hoping for everything to be nice and safe when the value comes back out.
Wrapping a value in something like Rc<RefCell<Option<_>>>
works, but is slightly cumbersome to work with. It also requires you to use an owned value. If you only have a mutable reference on hand, it seems like you cannot use it as a UserData. Scopes can create functions which are not 'static
so you can use borrowed values there, but this is also somewhat cumbersome.
I have tried to come up with a workaround for this, allowing Scope to create UserData from mut-borrowed values. Hoping to get opinions on whether this is implementation will work. Apologies if I have missed something obvious or something that has already been discussed. I have looked at #20, especially #20 (comment) .
The idea is to give a &'scope mut T
reference to the Scope
and store it as a raw pointer *mut T
, which is plain old data so it can have 'static
lifetime. Because we are in a Scope
, we know that the pointer will be deleted when the Scope
is destructed. The UserData
trait can be implemented for BorrowedUserData<T>
by running T::add_methods
with a modified UserDataMethods
implementation which extracts BorrowedUserData<T>
and dereferences the pointer whenever the T
is needed. Checking for multiple borrows is handled with in the same way as owned values, with RefCell.
Code diff here: luteberget@8d32cca
I am certainly no expert in unsafe Rust, so if I've misunderstood any of the following then this might not work:
&'scope mut T
reference ensures that the corresponding *mut T
points to the T
for the duration of the borrow.&T
or &mut T
in Rust code for the lifetime of the Ref
/RefMut
.mem::transmute
into static lifetime instead, or something else.Also, the BorrowedUserData<T>
struct is internal to the crate, so users of the library cannot use AnyUserData::borrow
or similar. I think this could be fixed by checking for both T
and BorrowedUserData<T>
when borrowing from AnyUserData
.
Using this feature looks like this:
extern crate rlua;
use rlua::prelude::*;
#[derive(Debug)]
struct MyData {
val: u64,
}
impl LuaUserData for MyData {
fn add_methods(methods: &mut LuaUserDataMethods<Self>) {
methods.add_method_mut("increment", |_,this,()| {
this.val += 1;
Ok(())
});
}
}
fn main() -> LuaResult<()> {
let lua = Lua::new();
let mut x = MyData { val: 0 };
lua.scope(|s| {
lua.globals().set("x", s.borrow_userdata(&mut x)?)?;
// mutate x from Lua
lua.eval::<()>("x:increment()", None)?;
Ok(())
})?;
x.val += 1;
// x.val is now 2
Ok(())
}
Hi.
When implementing ToLua
/FromLua
for custom types it would've been handy to have access to type name for returning it in ConversionError
. Maybe make it public? Or add utility method for creating ConversionError
s with apropriate to/from values?
In Rust, T::from*
and T::to*
imply creating a T
from another type or converting the T
to another type (doesn't have to consume T
).
Lua::from
doesn't create a Lua
object, it merely uses it to perform the conversion, producing a Value
. Similarly, Lua::to
doesn't convert the Lua
object, it converts its argument (a Value
).
I think the best way to clear this up is to remove both methods - they are merely wrappers around t.to_lua
and T::from_lua
respectively, which is perfectly clear.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.