Hacker News new | ask | show | jobs
by eridius 3328 days ago
A long time ago I wrote bindings to Lua 5.1 (https://github.com/kballard/rust-lua), but there was a nasty problem where Lua uses longjmp() for errors (it can be configured to use C++ exceptions, but that doesn't work because you can't throw those past an extern C boundary without hitting undefined behavior). longjmp(), of course, will just skip right past the intervening stack frames, meaning if your Rust code calls into Lua and it throws an error, then unless you wrapped that call with a lua_pcall, any values that live on the stack of your Rust function will never call their destructors.

I admit I haven't bothered to research the current state of things, but how do more recent Lua bindings handle this? Does Lua 5.3 actually have a proper solution here, or do most bindings just wrap every single call with lua_pcall? I didn't do that in my bindings because I wanted to offer the full speed of Lua, but it's certainly an option.

2 comments

The typical solution in C is to wrap and anchor such objects in the Lua VM so that they'll be garbage collected if an error is thrown. There are various patterns for doing this--direct binding of individual objects, or indirectly anchoring through an opaque staging struct--but that's the general idea.

Because Lua supports coroutines with a stack independent from the system C (Rust) stack, you often want to be careful mixing your stack-allocated objects. Lua 5.2 added lua_callk which allows yielding and resuming coroutines across the C API (that is, "yielding" a coroutine with a nested C or Rust function invocation).

Leveraging Lua's awesome coroutine support is one of the biggest reasons to use Lua, IMO.

Also, Lua can safely recover from out-of-memory (OOM) scenarios. On OOM it will throw an error that can be safely caught. Any Lua API which might internally allocate memory can throw this error, not just the lua_call family of routines. Quality libraries are expected to also handle OOM, which usually can be accomplished the same way as handling any other error: wrapping and anchoring temporaries, directly or indirectly, in the Lua VM.

That's still pretty much the state of the world. I think that's a trade-off that makes sense as long as you're aware that you shouldn't be depending on drop functionality in callbacks(I would hope that your initial point is dostring/dofile and let that catch handle things).

Generally I've found that any time you're interacting with an FFI in Rust all bets are off and you need to be very aware of what your libraries do and what their runtime looks like(just in C/C++).

I had intended to write a compiler plugin for rust-lua that added a lint that would ensure you have nothing on the stack with Drop when you call a (potentially-error-throwing) Lua function. But I never got around to doing that because I stopped using rust-lua (I canned the one project I was doing that motivated rust-lua in the first place).