jcmoyer / rust-lua53

Lua 5.3 bindings for Rust
MIT License
158 stars 45 forks source link

How safe is State::error? #37

Open jugglerchris opened 8 years ago

jugglerchris commented 8 years ago

Hi, I'm trying to work out how safe calling the error method is, for reporting problems back to Lua code. It causes longjmp to unwind past a Rust function, which sounds undefined, but can it be safe if I'm careful?

I'd be interested in both "in practice ok" and "yes it's correct" type answers.

Thanks,

Chris

jcmoyer commented 8 years ago

From the reference:

12.3 Behavior considered undefined

Unwinding into Rust from foreign code or unwinding from Rust into foreign code. Rust's failure
system is not compatible with exception handling in other languages. Unwinding must be caught
and handled at FFI boundaries.

All bets are off if you raise a Lua error outside of a pcall. I think to properly handle this, LUAI_TRY and LUAI_THROW need to be redefined to call into the appropriate Rust functions to perform setup/teardown.

jugglerchris commented 8 years ago

Hi, Thanks for the response, which I think confirms what I thought, which is that I'm ok with errors from Lua code if I always use pcall, but there isn't a safe way to generate a Lua error from Rust code.

When thinking about what I could do about this, I thought it would be nice if the Rust function could be written like:

fn myFunction(state: &mut lua::State) -> Result<c_int, String> {
   ...stuff with try!() etc.
}

where an Err result could be translated into a lua_error lower down.

So it would go something like:

  1. Lua calls foo()
  2. foo, which could be Lua or C, is a wrapper which is the inverse of pcall, something like:

    ok, results = wrap_foo() -- wrap_foo is probably an upvalue to a generic C/Lua function.
    if ok then
      return results
    else
      error(results) -- error message
    end
  3. wrap_foo is an optional wrapper, now Rust, which calls real_foo, and returns true, results or false, error msg depending on the Result returned.

With those layers in place, I could then write reasonably natural Rust functions and have errors translated automatically.

I think I've got all I need to implement this on top of rust-lua53 without any changes your end, though it would be convenient (and I guess a little faster) if the library supported calling error safely directly from Rust; however that looks hard to me.

I'd be interested in any comments/suggestions/obvious problems with my mad scheme above, but I'm happy to close this issue now; but I'll leave that to you in case you want to implement anything.

sagebind commented 8 years ago

I've been working on some things quite similar to this here (see components/script/environment.rs). I've added a few convenience wrappers around lua::State, including ones that allow closures to be pushed onto the stack and functions with the signature:

fn my_function(environment: Environment) -> Result<i32, Box<Error>> {
    // ...
    Ok(1)
}

@jugglerchris Perhaps you'd be interested in collaborating on a higher-level Rust-Lua library? I've got some neat ideas that don't really fit into this project (from my understanding, this is meant to be a 1:1 Rust binding to Lua).

jugglerchris commented 8 years ago

@coderstephen I was planning on (eventually) splitting out what I have into a separate crate; I'd be happy to work together. I've implemented the scheme in my above comment and a way to safely share Rust types in what I think is a (dynamically) type-safe way with Lua. I'll try to prioritise getting that up somewhere, hopefully sometime this week.

jugglerchris commented 8 years ago

I've split out my in-development (badly documented, inconsistently named and so on) interface into a github repo: https://github.com/jugglerchris/rlua in case it's of interest. See the _push_closure method which takes a Rust function returning a Result, and wraps it via a small Lua stub which converts an Err into a call to the Lua error function. Similarly run_loaded_lua converts Lua errors back into a Rust Err.

It's all a bit messy and rough at the moment, but I'm happy for that to be a starting point for a more generally useful and Rustic (is that the word?) interface on top of this one.