Go to file
kyren 47db72cac4 Big API incompatible error change, remove dependency on error_chain
The current situation with error_chain is less than ideal, and there are lots of
conflicting interests that are impossible to meet at once.  Here is an
unorganized brain dump of the current situation, stay awhile and listen!

This change was triggered ultimately by the desire to make LuaError implement
Clone, and this is currently impossible with error_chain.  LuaError must
implement Clone to be a proper lua citizen that can live as userdata within a
lua runtime, because there is no way to limit what the lua runtime can do with a
received error.  Currently, this is solved by there being a rule that the error
will "expire" if the error is passed back into rust, and this is very
sub-optimal.  In fact, one could easily imagine a scenario where lua is for
example memoizing some function, and if the function has ever errored in the
past the function should continue returning the same error, and this situation
immediately fails with this restriciton in place.

Additionally, there are other more minor problems with error_chain which make
the API less good than it could be, or limit how we can use error_chain.  This
change has already solved a small bug in a Chucklefish project, where the
conversion from an external error type (Borrow[Mut]Error) was allowed but not
intended for user code, and was accidentally used.  Additionally, pattern
matching on error_chain errors, which should be common when dealing with Lua, is
less convenient than a hand rolled error type.

So, if we decide not to use error_chain, we now have a new set of problems if we
decide interoperability with error_chain is important.  The first problem we run
into is that there are two natural bounds for wrapped errors that we would
pick, (Error + Send + Sync), or just Error, and neither of them will
interoperate well with error_chain.  (Error + Send + Sync) means we can't wrap
error chain errors into LuaError::ExternalError (they're missing the Sync
bound), and having the bounds be just Error means the opposite, that we can't
hold a LuaError inside an error_chain error.

We could just decide that interoperability with error_chain is the most
important qualification, and pick (Error + Send), but this causes a DIFFERENT
set of problems.  The rust ecosystem has the two primary error bounds as Error
or (Error + Send + Sync), and there are Into impls from &str / String to
Box<Error + Send + Sync> for example, but NOT (Error + Send).  This means that
we are forced to manually recreate the conversions from &str / String to
LuaError rather than relying on a single Into<Box<Error + Send + Sync>> bound,
but this means that string conversions have a different set of methods than
other error types for external error conversion.  I have not been able to figure
out an API that I am happy with that uses the (Error + Send) bound.  Box<Error>
is obnoxious because not having errors implement Send causes needless problems
in a multithreaded context, so that leaves (Error + Send + Sync).  This is
actually a completely reasonable bound for external errors, and has the nice
String Into impls that we would want, the ONLY problem is that it is a pain to
interoperate with the current version of error_chain.

It would be nice to be able to specify the traits that an error generated by the
error_chain macro would implement, and this is apparently in progress in the
error_chain library.  This would solve both the problem with not being able to
implement Clone and the problems with (Error + Send) bounds.  I am not convinced
that this library should go back to using error_chain when that functionality is
in stable error_chain though, because of the other minor usability problems with
using error_chain.

In that theoretical situation, the downside of NOT using error_chain is simply
that there would not be automatic stacktraces of LuaError.  This is not a huge
problem, because stack traces of lua errors are not extremely useful, and for
external errors it is not too hard to create a different version of the
LuaExternalResult / LuaExternalError traits and do conversion from an
error_chain type into a type that will print the stacktrace on display, or
use downcasting in the error causes.

So in summary, this library is no longer using error_chain, and probably will
not use it again in the future.  Currently this means that to interoperate with
error_chain, you should use error_chain 0.8.1, which derives Sync on errors, or
wait for a version that supports user defined trait derives.  In the future
when error_chain supports user defined trait derives, users may have to take an
extra step to make wrapped external errors print the stacktrace that they
capture.

This change works, but is not entirely complete.  There is no error
documentation yet, and the change brought to a head an ugly module organization
problem.  There will be more commits for documentation and reorganization, then
a new stable version of rlua.
2017-06-24 18:11:56 -04:00
examples Big API incompatible error change, remove dependency on error_chain 2017-06-24 18:11:56 -04:00
lua remove stray lib.rs file 2017-05-29 16:33:14 -04:00
src Big API incompatible error change, remove dependency on error_chain 2017-06-24 18:11:56 -04:00
.gitignore Initial import 2017-05-21 19:50:59 -04:00
.travis.yml Add travis-ci script 2017-05-22 23:00:32 -04:00
Cargo.toml Big API incompatible error change, remove dependency on error_chain 2017-06-24 18:11:56 -04:00
LICENSE More sensible license, show that copyright is to Chucklefish LTD 2017-05-21 22:58:47 -04:00
README.md Fix link 2017-05-29 16:30:43 -04:00
build.rs enable more lua platform defines in build.rs 2017-06-17 16:56:36 -04:00

README.md

rlua -- High level bindings between Rust and Lua

Build Status

WIP API Documentation

This library is a WIP high level interface between Rust and Lua. Its major goal is to expose as easy to use, practical, and 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 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 have non-mutable borrows to 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 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.

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:

  • 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 custom metatables and exposing the registry.
  • MUCH better API documentation, the current API documentation is almost 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.
  • Once tuple based variadic generics land, the plan is to completely eliminate the hlist macros in favor of simple tuples.

See this reddit discussion for details of the current lifetime problem with callback wrapping.

It is also worth it to list some non-goals for the project:

  • Be a perfect zero cost wrapper over the lua C api
  • Allow the user to do absolutely everything that the lua C api might allow

API Stability or lack thereof

This library is very much Work In Progress, so there may be a lot of API churn. I will try to follow a pre-1.0 semver (if such a thing exists), but that means there will just be a large number of API bumps.

Safety

My goal is complete safety, it should not be possible to cause undefined behavior whatsoever with the API, even in edge cases. There is, however, QUITE a lot of unsafe code in this crate, and I would call the current safety level of the crate "Work In Progress". The GOAL is for the crate to handle tricky situations such as:

  • Panic safety, and carrying the panic across the lua api correctly
  • Passing rust panics across the lua boundary as lua errors without allowing lua to catch rust panics as normal errors.
  • Lua stack size checking, and correctly handling lua being out of stack space
  • Leaving the correct elements on the lua stack and in the correct order, and panicking if these invariants are not met (due to internal bugs).
  • Correctly guarding the metatables of userdata so that scripts cannot, for example, swap the __gc methods of userdata and cause UB.
  • Correctly handling complex recursive callback scenarios where control goes from rust to lua back to rust back to lua and so forth.

The library currently attempts to handle each of these situations, but there are so many ways to cause unsafety with Lua that it just needs more testing.

Examples

Please look at the examples here.